mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-22 00:26:34 +03:00
fix: rewrite recursive checks (#2072)
Co-authored-by: Roman Hotsiy <gotsijroman@gmail.com>
This commit is contained in:
parent
9920991080
commit
2970f959cf
13
package-lock.json
generated
13
package-lock.json
generated
|
@ -81,6 +81,7 @@
|
|||
"license-checker": "^25.0.1",
|
||||
"lodash.noop": "^3.0.1",
|
||||
"mobx": "^6.3.2",
|
||||
"outdent": "^0.8.0",
|
||||
"prettier": "^2.3.2",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"raf": "^3.4.1",
|
||||
|
@ -14282,6 +14283,12 @@
|
|||
"integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/outdent": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz",
|
||||
"integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/p-each-series": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
|
||||
|
@ -29936,6 +29943,12 @@
|
|||
"integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=",
|
||||
"dev": true
|
||||
},
|
||||
"outdent": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz",
|
||||
"integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==",
|
||||
"dev": true
|
||||
},
|
||||
"p-each-series": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
"license-checker": "^25.0.1",
|
||||
"lodash.noop": "^3.0.1",
|
||||
"mobx": "^6.3.2",
|
||||
"outdent": "^0.8.0",
|
||||
"prettier": "^2.3.2",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"raf": "^3.4.1",
|
||||
|
@ -193,10 +194,12 @@
|
|||
"coveragePathIgnorePatterns": [
|
||||
"\\.d\\.ts$",
|
||||
"/benchmark/",
|
||||
"/node_modules/"
|
||||
"/node_modules/",
|
||||
"src/services/__tests__/models/helpers.ts"
|
||||
],
|
||||
"modulePathIgnorePatterns": [
|
||||
"/benchmark/"
|
||||
"/benchmark/",
|
||||
"src/services/__tests__/models/helpers.ts"
|
||||
],
|
||||
"snapshotSerializers": [
|
||||
"enzyme-to-json/serializer"
|
||||
|
|
|
@ -4,8 +4,8 @@ import * as React from 'react';
|
|||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
||||
import { GroupModel, OperationModel } from '../../services/models';
|
||||
import type { ContentItemModel } from '../../services';
|
||||
import type { GroupModel, OperationModel } from '../../services/models';
|
||||
import { Operation } from '../Operation/Operation';
|
||||
|
||||
@observer
|
||||
|
|
16
src/components/Schema/RecursiveSchema.tsx
Normal file
16
src/components/Schema/RecursiveSchema.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import * as React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
|
||||
import { l } from '../../services/Labels';
|
||||
import type { SchemaProps } from '.';
|
||||
|
||||
export const RecursiveSchema = observer(({ schema }: SchemaProps) => {
|
||||
return (
|
||||
<div>
|
||||
<TypeName>{schema.displayType}</TypeName>
|
||||
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
||||
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
||||
</div>
|
||||
);
|
||||
});
|
|
@ -1,7 +1,6 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
|
||||
import { FieldDetails } from '../Fields/FieldDetails';
|
||||
|
||||
import { FieldModel, SchemaModel } from '../../services/models';
|
||||
|
@ -9,8 +8,8 @@ import { FieldModel, SchemaModel } from '../../services/models';
|
|||
import { ArraySchema } from './ArraySchema';
|
||||
import { ObjectSchema } from './ObjectSchema';
|
||||
import { OneOfSchema } from './OneOfSchema';
|
||||
import { RecursiveSchema } from './RecursiveSchema';
|
||||
|
||||
import { l } from '../../services/Labels';
|
||||
import { isArray } from '../../utils/helpers';
|
||||
|
||||
export interface SchemaOptions {
|
||||
|
@ -36,13 +35,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
const { type, oneOf, discriminatorProp, isCircular } = schema;
|
||||
|
||||
if (isCircular) {
|
||||
return (
|
||||
<div>
|
||||
<TypeName>{schema.displayType}</TypeName>
|
||||
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
||||
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
||||
</div>
|
||||
);
|
||||
return <RecursiveSchema schema={schema} />;
|
||||
}
|
||||
|
||||
if (discriminatorProp !== undefined) {
|
||||
|
@ -52,11 +45,14 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
const activeSchema = oneOf[schema.activeOneOf];
|
||||
return activeSchema.isCircular ? (
|
||||
<RecursiveSchema schema={activeSchema} />
|
||||
) : (
|
||||
<ObjectSchema
|
||||
{...rest}
|
||||
level={level}
|
||||
schema={oneOf![schema.activeOneOf]}
|
||||
schema={activeSchema}
|
||||
discriminator={{
|
||||
fieldName: discriminatorProp,
|
||||
parentSchema: schema,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { IMenuItem } from '../../services/MenuStore';
|
||||
import { SearchStore } from '../../services/SearchStore';
|
||||
import type { IMenuItem, SearchResult } from '../../services/types';
|
||||
import type { SearchStore } from '../../services/SearchStore';
|
||||
import type { MarkerService } from '../../services/MarkerService';
|
||||
|
||||
import { MenuItem } from '../SideMenu/MenuItem';
|
||||
|
||||
import { MarkerService } from '../../services/MarkerService';
|
||||
import { SearchResult } from '../../services/SearchWorker.worker';
|
||||
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { bind, debounce } from 'decko';
|
||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||
|
|
|
@ -2,13 +2,14 @@ import { observer } from 'mobx-react';
|
|||
import * as React from 'react';
|
||||
|
||||
import { ShelfIcon } from '../../common-elements/shelfs';
|
||||
import { IMenuItem, OperationModel } from '../../services';
|
||||
import { OperationModel } from '../../services';
|
||||
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||
import { MenuItems } from './MenuItems';
|
||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||
import { l } from '../../services/Labels';
|
||||
import { scrollIntoViewIfNeeded } from '../../utils';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import type { IMenuItem } from '../../services';
|
||||
|
||||
export interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { IMenuItem } from '../../services';
|
||||
import type { IMenuItem } from '../../services';
|
||||
|
||||
import { MenuItem } from './MenuItem';
|
||||
import { MenuItemUl } from './styled.elements';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
||||
import { MenuStore } from '../../services';
|
||||
import type { IMenuItem } from '../../services';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { MenuItems } from './MenuItems';
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -70,7 +70,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -300,6 +300,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "number",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog/properties/packSize",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
|
@ -337,7 +341,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -565,11 +569,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog/properties/type",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "",
|
||||
"type": "string",
|
||||
|
@ -579,7 +599,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
},
|
||||
],
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -818,21 +838,24 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "object",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
],
|
||||
"schema": Object {
|
||||
"allOf": undefined,
|
||||
"discriminator": Object {
|
||||
"propertyName": "type",
|
||||
},
|
||||
"format": undefined,
|
||||
"parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
"properties": Object {
|
||||
"packSize": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"type": Object {
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
},
|
||||
"required": Array [
|
||||
|
@ -840,6 +863,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
],
|
||||
"title": "Dog",
|
||||
"type": "object",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "Dog",
|
||||
"type": "object",
|
||||
|
@ -889,7 +916,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -1117,11 +1144,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Cat/properties/type",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "",
|
||||
"type": "string",
|
||||
|
@ -1156,7 +1199,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -1384,11 +1427,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
],
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Cat/properties/packSize",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
],
|
||||
},
|
||||
"title": "",
|
||||
"type": "number",
|
||||
|
@ -1398,7 +1453,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
},
|
||||
],
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -1639,21 +1694,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "object",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
],
|
||||
"schema": Object {
|
||||
"allOf": undefined,
|
||||
"discriminator": Object {
|
||||
"propertyName": "type",
|
||||
},
|
||||
"format": undefined,
|
||||
"parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
"properties": Object {
|
||||
"packSize": Object {
|
||||
"type": "number",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
],
|
||||
},
|
||||
"type": Object {
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Cat",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
},
|
||||
"required": Array [
|
||||
|
@ -1661,6 +1722,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
],
|
||||
"title": "Cat",
|
||||
"type": "object",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "Cat",
|
||||
"type": "object",
|
||||
|
@ -1904,6 +1969,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "object",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
"schema": Object {
|
||||
"discriminator": Object {
|
||||
"propertyName": "type",
|
||||
|
@ -1970,7 +2038,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -2200,6 +2268,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "number",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog/properties/packSize",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
|
@ -2237,7 +2309,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -2465,11 +2537,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog/properties/type",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "",
|
||||
"type": "string",
|
||||
|
@ -2479,7 +2567,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
},
|
||||
],
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -2718,21 +2806,24 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
"type": "object",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
],
|
||||
"schema": Object {
|
||||
"allOf": undefined,
|
||||
"discriminator": Object {
|
||||
"propertyName": "type",
|
||||
},
|
||||
"format": undefined,
|
||||
"parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
"properties": Object {
|
||||
"packSize": Object {
|
||||
"type": "number",
|
||||
},
|
||||
"type": Object {
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
},
|
||||
"required": Array [
|
||||
|
@ -2740,6 +2831,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
|||
],
|
||||
"title": "Dog",
|
||||
"type": "object",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "Dog",
|
||||
"type": "object",
|
||||
|
@ -2783,7 +2878,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -2795,6 +2890,10 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"type": "number",
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog/properties/packSize",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "number",
|
||||
|
@ -2840,7 +2939,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"examples": undefined,
|
||||
"externalDocs": undefined,
|
||||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isCircular": false,
|
||||
"isPrimitive": true,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
|
@ -2850,11 +2949,27 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"rawSchema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"readOnly": false,
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
"#/components/schemas/Dog/properties/type",
|
||||
],
|
||||
"schema": Object {
|
||||
"default": undefined,
|
||||
"type": "string",
|
||||
"x-refsStack": Array [
|
||||
"#/components/schemas/Dog",
|
||||
"#/components/schemas/Pet",
|
||||
],
|
||||
},
|
||||
"title": "",
|
||||
"type": "string",
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Lambda, observe } from 'mobx';
|
||||
|
||||
import { OpenAPISpec } from '../types';
|
||||
import type { OpenAPISpec } from '../types';
|
||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||
import { history } from './HistoryService';
|
||||
import { MarkerService } from './MarkerService';
|
||||
import { MenuStore } from './MenuStore';
|
||||
import { SpecStore } from './models';
|
||||
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { RedocRawOptions } from './RedocNormalizedOptions';
|
||||
import { ScrollService } from './ScrollService';
|
||||
import { SearchStore } from './SearchStore';
|
||||
|
||||
|
@ -15,18 +16,7 @@ import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
|||
import { SCHEMA_DEFINITION_JSX_NAME, SECURITY_DEFINITIONS_JSX_NAME } from '../utils/openapi';
|
||||
|
||||
import { IS_BROWSER } from '../utils';
|
||||
|
||||
export interface StoreState {
|
||||
menu: {
|
||||
activeItemIdx: number;
|
||||
};
|
||||
spec: {
|
||||
url?: string;
|
||||
data: any;
|
||||
};
|
||||
searchIndex: any;
|
||||
options: RedocRawOptions;
|
||||
}
|
||||
import type { StoreState } from './types';
|
||||
|
||||
export async function createStore(
|
||||
spec: object,
|
||||
|
|
|
@ -1,25 +1,4 @@
|
|||
export interface LabelsConfig {
|
||||
enum: string;
|
||||
enumSingleValue: string;
|
||||
enumArray: string;
|
||||
default: string;
|
||||
deprecated: string;
|
||||
example: string;
|
||||
examples: string;
|
||||
recursive: string;
|
||||
arrayOf: string;
|
||||
webhook: string;
|
||||
const: string;
|
||||
noResultsFound: string;
|
||||
download: string;
|
||||
downloadSpecification: string;
|
||||
responses: string;
|
||||
callbackResponses: string;
|
||||
requestSamples: string;
|
||||
responseSamples: string;
|
||||
}
|
||||
|
||||
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
||||
import type { LabelsConfig, LabelsConfigRaw } from './types';
|
||||
|
||||
const labels: LabelsConfig = {
|
||||
enum: 'Enum',
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||
import { AppStore } from './AppStore';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { MarkdownHeading, MDXComponentMeta } from './types';
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
|
@ -22,20 +21,6 @@ export const MDX_COMPONENT_REGEXP = '(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\
|
|||
|
||||
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
||||
|
||||
export interface MDXComponentMeta {
|
||||
component: React.ComponentType;
|
||||
propsSelector: (store?: AppStore) => any;
|
||||
props?: object;
|
||||
}
|
||||
|
||||
export interface MarkdownHeading {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
items?: MarkdownHeading[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function buildComponentComment(name: string) {
|
||||
return `<!-- ReDoc-Inject: <${name}> -->`;
|
||||
}
|
||||
|
|
|
@ -1,41 +1,12 @@
|
|||
import {
|
||||
OpenAPIOperation,
|
||||
OpenAPIParameter,
|
||||
OpenAPISpec,
|
||||
OpenAPITag,
|
||||
Referenced,
|
||||
OpenAPIServer,
|
||||
OpenAPIPaths,
|
||||
} from '../types';
|
||||
import type { OpenAPISpec, OpenAPIPaths } from '../types';
|
||||
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { GroupModel, OperationModel } from './models';
|
||||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
export type TagInfo = OpenAPITag & {
|
||||
operations: ExtendedOpenAPIOperation[];
|
||||
used?: boolean;
|
||||
};
|
||||
|
||||
export type ExtendedOpenAPIOperation = {
|
||||
pointer: string;
|
||||
pathName: string;
|
||||
httpVerb: string;
|
||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||
pathServers: Array<OpenAPIServer> | undefined;
|
||||
isWebhook: boolean;
|
||||
} & OpenAPIOperation;
|
||||
|
||||
export type TagsInfoMap = Record<string, TagInfo>;
|
||||
|
||||
export interface TagGroup {
|
||||
name: string;
|
||||
tags: string[];
|
||||
}
|
||||
import type { OpenAPIParser } from './OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { ContentItemModel, TagGroup, TagInfo, TagsInfoMap } from './types';
|
||||
|
||||
export const GROUP_DEPTH = 0;
|
||||
export type ContentItemModel = GroupModel | OperationModel;
|
||||
|
||||
export class MenuBuilder {
|
||||
/**
|
||||
|
@ -239,7 +210,7 @@ export class MenuBuilder {
|
|||
for (const operationName of operations) {
|
||||
const operationInfo = path[operationName];
|
||||
if (path.$ref) {
|
||||
const resolvedPaths = parser.deref<OpenAPIPaths>(path as OpenAPIPaths);
|
||||
const { resolved: resolvedPaths } = parser.deref<OpenAPIPaths>(path as OpenAPIPaths);
|
||||
getTags(parser, { [pathName]: resolvedPaths }, isWebhook);
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -1,37 +1,15 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
import { querySelector } from '../utils/dom';
|
||||
import { SpecStore } from './models';
|
||||
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||
|
||||
import { history as historyInst, HistoryService } from './HistoryService';
|
||||
import { ScrollService } from './ScrollService';
|
||||
|
||||
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||
import { GROUP_DEPTH } from './MenuBuilder';
|
||||
|
||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||
export type MenuItemType = MenuItemGroupType | 'operation';
|
||||
import type { SpecStore } from './models';
|
||||
import type { ScrollService } from './ScrollService';
|
||||
import type { IMenuItem } from './types';
|
||||
|
||||
/** Generic interface for MenuItems */
|
||||
export interface IMenuItem {
|
||||
id: string;
|
||||
absoluteIdx?: number;
|
||||
name: string;
|
||||
sidebarLabel: string;
|
||||
description?: string;
|
||||
depth: number;
|
||||
active: boolean;
|
||||
expanded: boolean;
|
||||
items: IMenuItem[];
|
||||
parent?: IMenuItem;
|
||||
deprecated?: boolean;
|
||||
type: MenuItemType;
|
||||
|
||||
deactivate(): void;
|
||||
activate(): void;
|
||||
|
||||
collapse(): void;
|
||||
expand(): void;
|
||||
}
|
||||
|
||||
export const SECTION_ATTR = 'data-section-id';
|
||||
|
||||
|
|
|
@ -1,45 +1,27 @@
|
|||
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
||||
|
||||
import { isArray, isBoolean, IS_BROWSER } from '../utils';
|
||||
import type { OpenAPIRef, OpenAPISchema, OpenAPISpec } from '../types';
|
||||
import { IS_BROWSER, getDefinitionName } from '../utils/';
|
||||
import { JsonPointer } from '../utils/JsonPointer';
|
||||
import { getDefinitionName, isNamedDefinition } from '../utils/openapi';
|
||||
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
|
||||
|
||||
/**
|
||||
* Helper class to keep track of visited references to avoid
|
||||
* endless recursion because of circular refs
|
||||
*/
|
||||
class RefCounter {
|
||||
_counter = {};
|
||||
|
||||
reset(): void {
|
||||
this._counter = {};
|
||||
}
|
||||
|
||||
visit(ref: string): void {
|
||||
this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1;
|
||||
}
|
||||
|
||||
exit(ref: string): void {
|
||||
this._counter[ref] = this._counter[ref] && this._counter[ref] - 1;
|
||||
}
|
||||
|
||||
visited(ref: string): boolean {
|
||||
return !!this._counter[ref];
|
||||
}
|
||||
}
|
||||
import type { MergedOpenAPISchema } from './types';
|
||||
|
||||
/**
|
||||
* Loads and keeps spec. Provides raw spec operations
|
||||
*/
|
||||
|
||||
export function pushRef(stack: string[], ref?: string): string[] {
|
||||
return ref && stack[stack.length - 1] !== ref ? [...stack, ref] : stack;
|
||||
}
|
||||
|
||||
export function concatRefStacks(base: string[], stack?: string[]): string[] {
|
||||
return stack ? base.concat(stack) : base;
|
||||
}
|
||||
|
||||
export class OpenAPIParser {
|
||||
specUrl?: string;
|
||||
spec: OpenAPISpec;
|
||||
|
||||
private _refCounter: RefCounter = new RefCounter();
|
||||
private allowMergeRefs: boolean = false;
|
||||
private readonly allowMergeRefs: boolean = false;
|
||||
|
||||
constructor(
|
||||
spec: OpenAPISpec,
|
||||
|
@ -51,13 +33,13 @@ export class OpenAPIParser {
|
|||
this.spec = spec;
|
||||
this.allowMergeRefs = spec.openapi.startsWith('3.1');
|
||||
|
||||
const href = IS_BROWSER ? window.location.href : undefined;
|
||||
const href = IS_BROWSER ? window.location.href : '';
|
||||
if (typeof specUrl === 'string') {
|
||||
this.specUrl = new URL(specUrl, href).href;
|
||||
}
|
||||
}
|
||||
|
||||
validate(spec: any) {
|
||||
validate(spec: GenericObject): void {
|
||||
if (spec.openapi === undefined) {
|
||||
throw new Error('Document must be valid OpenAPI 3.0.0 definition');
|
||||
}
|
||||
|
@ -86,101 +68,82 @@ export class OpenAPIParser {
|
|||
/**
|
||||
* checks if the object is OpenAPI reference (contains $ref property)
|
||||
*/
|
||||
isRef(obj: any): obj is OpenAPIRef {
|
||||
isRef<T extends unknown>(obj: OpenAPIRef | T): obj is OpenAPIRef {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
obj = <OpenAPIRef>obj;
|
||||
return obj.$ref !== undefined && obj.$ref !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* resets visited endpoints. should be run after
|
||||
*/
|
||||
resetVisited() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// check in dev mode
|
||||
for (const k in this._refCounter._counter) {
|
||||
if (this._refCounter._counter[k] > 0) {
|
||||
console.warn('Not exited reference: ' + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._refCounter = new RefCounter();
|
||||
}
|
||||
|
||||
exitRef<T>(ref: Referenced<T>) {
|
||||
if (!this.isRef(ref)) {
|
||||
return;
|
||||
}
|
||||
this._refCounter.exit(ref.$ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve given reference object or return as is if it is not a reference
|
||||
* @param obj object to dereference
|
||||
* @param forceCircular whether to dereference even if it is circular ref
|
||||
* @param mergeAsAllOf
|
||||
*/
|
||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false, mergeAsAllOf = false): T {
|
||||
deref<T extends unknown>(
|
||||
obj: OpenAPIRef | T,
|
||||
baseRefsStack: string[] = [],
|
||||
mergeAsAllOf = false,
|
||||
): { resolved: T; refsStack: string[] } {
|
||||
// this can be set by all of when it mergers props from different sources
|
||||
const objRefsStack = obj?.['x-refsStack'];
|
||||
baseRefsStack = concatRefStacks(baseRefsStack, objRefsStack);
|
||||
|
||||
if (this.isRef(obj)) {
|
||||
const schemaName = getDefinitionName(obj.$ref);
|
||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||
return { type: 'object', title: schemaName } as T;
|
||||
return { resolved: { type: 'object', title: schemaName } as T, refsStack: baseRefsStack };
|
||||
}
|
||||
|
||||
const resolved = this.byRef<T>(obj.$ref)!;
|
||||
const visited = this._refCounter.visited(obj.$ref);
|
||||
this._refCounter.visit(obj.$ref);
|
||||
if (visited && !forceCircular) {
|
||||
// circular reference detected
|
||||
// tslint:disable-next-line
|
||||
return Object.assign({}, resolved, { 'x-circular-ref': true });
|
||||
let resolved = this.byRef<T>(obj.$ref);
|
||||
if (!resolved) {
|
||||
throw new Error(`Failed to resolve $ref "${obj.$ref}"`);
|
||||
}
|
||||
// deref again in case one more $ref is here
|
||||
let result = resolved;
|
||||
if (this.isRef(resolved)) {
|
||||
result = this.deref(resolved, false, mergeAsAllOf);
|
||||
this.exitRef(resolved);
|
||||
|
||||
let refsStack = baseRefsStack;
|
||||
if (baseRefsStack.includes(obj.$ref)) {
|
||||
resolved = Object.assign({}, resolved, { 'x-circular-ref': true });
|
||||
} else if (this.isRef(resolved)) {
|
||||
const res = this.deref(resolved, baseRefsStack, mergeAsAllOf);
|
||||
refsStack = res.refsStack;
|
||||
resolved = res.resolved;
|
||||
}
|
||||
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : result;
|
||||
|
||||
refsStack = pushRef(baseRefsStack, obj.$ref);
|
||||
resolved = this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : resolved;
|
||||
|
||||
return { resolved, refsStack };
|
||||
}
|
||||
return obj;
|
||||
return {
|
||||
resolved: obj,
|
||||
refsStack: concatRefStacks(baseRefsStack, objRefsStack),
|
||||
};
|
||||
}
|
||||
|
||||
shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T {
|
||||
if (this.isRef(obj)) {
|
||||
const schemaName = getDefinitionName(obj.$ref);
|
||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||
return { type: 'object', title: schemaName } as T;
|
||||
}
|
||||
const resolved = this.byRef<T>(obj.$ref);
|
||||
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, false) : (resolved as T);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
||||
mergeRefs<T extends unknown>(ref: OpenAPIRef, resolved: T, mergeAsAllOf: boolean): T {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { $ref, ...rest } = ref;
|
||||
const keys = Object.keys(rest);
|
||||
if (keys.length === 0) {
|
||||
if (this.isRef(resolved)) {
|
||||
return this.shallowDeref(resolved);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
if (
|
||||
mergeAsAllOf &&
|
||||
keys.some(k => k !== 'description' && k !== 'title' && k !== 'externalDocs')
|
||||
keys.some(
|
||||
k => !['description', 'title', 'externalDocs', 'x-refsStack', 'x-parentRefs'].includes(k),
|
||||
)
|
||||
) {
|
||||
return {
|
||||
allOf: [resolved, rest],
|
||||
};
|
||||
} as T;
|
||||
} else {
|
||||
// small optimization
|
||||
return {
|
||||
...resolved,
|
||||
...(resolved as GenericObject),
|
||||
...rest,
|
||||
};
|
||||
} as T;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,15 +152,15 @@ export class OpenAPIParser {
|
|||
* @param schema schema with allOF
|
||||
* @param $ref pointer of the schema
|
||||
* @param forceCircular whether to dereference children even if it is a circular ref
|
||||
* @param used$Refs
|
||||
*/
|
||||
mergeAllOf(
|
||||
schema: OpenAPISchema,
|
||||
$ref?: string,
|
||||
forceCircular: boolean = false,
|
||||
used$Refs = new Set<string>(),
|
||||
schema: MergedOpenAPISchema,
|
||||
$ref: string | undefined,
|
||||
refsStack: string[],
|
||||
): MergedOpenAPISchema {
|
||||
if ($ref) {
|
||||
used$Refs.add($ref);
|
||||
if (schema['x-circular-ref']) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
schema = this.hoistOneOfs(schema);
|
||||
|
@ -208,8 +171,8 @@ export class OpenAPIParser {
|
|||
|
||||
let receiver: MergedOpenAPISchema = {
|
||||
...schema,
|
||||
'x-parentRefs': [],
|
||||
allOf: undefined,
|
||||
parentRefs: [],
|
||||
title: schema.title || getDefinitionName($ref),
|
||||
};
|
||||
|
||||
|
@ -222,36 +185,41 @@ export class OpenAPIParser {
|
|||
}
|
||||
|
||||
const allOfSchemas = schema.allOf
|
||||
.map(subSchema => {
|
||||
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
||||
.map((subSchema: OpenAPISchema) => {
|
||||
const { resolved, refsStack: subRefsStack } = this.deref(subSchema, refsStack, true);
|
||||
|
||||
const subRef = subSchema.$ref || undefined;
|
||||
const subMerged = this.mergeAllOf(resolved, subRef, subRefsStack);
|
||||
if (subMerged['x-circular-ref'] && subMerged.allOf) {
|
||||
// if mergeAllOf is circular and still contains allOf, we should ignore it
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolved = this.deref(subSchema, forceCircular, true);
|
||||
const subRef = subSchema.$ref || undefined;
|
||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||
if (subRef) {
|
||||
// collect information for implicit descriminator lookup
|
||||
receiver['x-parentRefs']?.push(...(subMerged['x-parentRefs'] || []), subRef);
|
||||
}
|
||||
return {
|
||||
$ref: subRef,
|
||||
refsStack: pushRef(subRefsStack, subRef),
|
||||
schema: subMerged,
|
||||
};
|
||||
})
|
||||
.filter(child => child !== undefined) as Array<{
|
||||
$ref: string | undefined;
|
||||
schema: MergedOpenAPISchema;
|
||||
refsStack: string[];
|
||||
}>;
|
||||
|
||||
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
||||
for (const { schema: subSchema, refsStack: subRefsStack } of allOfSchemas) {
|
||||
const {
|
||||
type,
|
||||
format,
|
||||
enum: enumProperty,
|
||||
properties,
|
||||
items,
|
||||
required,
|
||||
title,
|
||||
oneOf,
|
||||
anyOf,
|
||||
title,
|
||||
'x-circular-ref': isCircular,
|
||||
...otherConstraints
|
||||
} = subSchema;
|
||||
|
||||
|
@ -264,7 +232,6 @@ export class OpenAPIParser {
|
|||
receiver.type = [...type, ...receiver.type];
|
||||
} else {
|
||||
receiver.type = type;
|
||||
receiver.format = format;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,43 +243,51 @@ export class OpenAPIParser {
|
|||
}
|
||||
}
|
||||
|
||||
if (properties !== undefined) {
|
||||
if (properties !== undefined && typeof properties === 'object') {
|
||||
receiver.properties = receiver.properties || {};
|
||||
for (const prop in properties) {
|
||||
const propRefsStack = concatRefStacks(subRefsStack, properties[prop]?.['x-refsStack']);
|
||||
if (!receiver.properties[prop]) {
|
||||
receiver.properties[prop] = properties[prop];
|
||||
} else {
|
||||
receiver.properties[prop] = {
|
||||
...properties[prop],
|
||||
'x-refsStack': propRefsStack,
|
||||
} as MergedOpenAPISchema;
|
||||
} else if (!isCircular) {
|
||||
// merge inner properties
|
||||
const mergedProp = this.mergeAllOf(
|
||||
{ allOf: [receiver.properties[prop], properties[prop]] },
|
||||
{
|
||||
allOf: [receiver.properties[prop], properties[prop]],
|
||||
'x-refsStack': propRefsStack,
|
||||
},
|
||||
$ref + '/properties/' + prop,
|
||||
propRefsStack,
|
||||
);
|
||||
receiver.properties[prop] = mergedProp;
|
||||
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (items !== undefined) {
|
||||
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);
|
||||
if (items !== undefined && !isCircular) {
|
||||
// FIXME: this is invalid here, we need to fix it in separate PR
|
||||
const receiverItems =
|
||||
typeof receiver.items === 'boolean'
|
||||
? { items: receiver.items }
|
||||
: receiver.items
|
||||
? (Object.assign({}, receiver.items) as OpenAPISchema)
|
||||
: {};
|
||||
const subSchemaItems =
|
||||
typeof subSchema.items === 'boolean'
|
||||
? { items: subSchema.items }
|
||||
: (Object.assign({}, subSchema.items) as OpenAPISchema);
|
||||
// merge inner properties
|
||||
receiver.items = this.mergeAllOf(
|
||||
{ allOf: [receiverItems, subSchemaItems] },
|
||||
{
|
||||
allOf: [receiverItems, subSchemaItems],
|
||||
},
|
||||
$ref + '/items',
|
||||
subRefsStack,
|
||||
);
|
||||
}
|
||||
|
||||
if (required !== undefined) {
|
||||
receiver.required = (receiver.required || []).concat(required);
|
||||
}
|
||||
|
||||
if (oneOf !== undefined) {
|
||||
receiver.oneOf = oneOf;
|
||||
}
|
||||
|
@ -321,18 +296,18 @@ export class OpenAPIParser {
|
|||
receiver.anyOf = anyOf;
|
||||
}
|
||||
|
||||
if (required !== undefined) {
|
||||
receiver.required = [...(receiver.required || []), ...required];
|
||||
}
|
||||
|
||||
// merge rest of constraints
|
||||
// TODO: do more intelligent merge
|
||||
receiver = { ...receiver, title: receiver.title || title, ...otherConstraints };
|
||||
|
||||
if (subSchemaRef) {
|
||||
receiver.parentRefs!.push(subSchemaRef);
|
||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
||||
// this is not so correct behaviour. commented out for now
|
||||
// ref: https://github.com/Redocly/redoc/issues/601
|
||||
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
||||
}
|
||||
}
|
||||
receiver = {
|
||||
...receiver,
|
||||
title: receiver.title || title,
|
||||
'x-circular-ref': receiver['x-circular-ref'] || isCircular,
|
||||
...otherConstraints,
|
||||
};
|
||||
}
|
||||
|
||||
return receiver;
|
||||
|
@ -347,10 +322,12 @@ export class OpenAPIParser {
|
|||
const res: Record<string, string[]> = {};
|
||||
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
||||
for (const defName in schemas) {
|
||||
const def = this.deref(schemas[defName]);
|
||||
const { resolved: def } = this.deref(schemas[defName]);
|
||||
if (
|
||||
def.allOf !== undefined &&
|
||||
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
||||
def.allOf.find(
|
||||
(obj: OpenAPISchema) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1,
|
||||
)
|
||||
) {
|
||||
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||
}
|
||||
|
@ -358,12 +335,6 @@ export class OpenAPIParser {
|
|||
return res;
|
||||
}
|
||||
|
||||
exitParents(shema: MergedOpenAPISchema) {
|
||||
for (const parent$ref of shema.parentRefs || []) {
|
||||
this.exitRef({ $ref: parent$ref });
|
||||
}
|
||||
}
|
||||
|
||||
private hoistOneOfs(schema: OpenAPISchema) {
|
||||
if (schema.allOf === undefined) {
|
||||
return schema;
|
||||
|
@ -372,19 +343,14 @@ export class OpenAPIParser {
|
|||
const allOf = schema.allOf;
|
||||
for (let i = 0; i < allOf.length; i++) {
|
||||
const sub = allOf[i];
|
||||
if (isArray(sub.oneOf)) {
|
||||
if (Array.isArray(sub.oneOf)) {
|
||||
const beforeAllOf = allOf.slice(0, i);
|
||||
const afterAllOf = allOf.slice(i + 1);
|
||||
return {
|
||||
oneOf: sub.oneOf.map(part => {
|
||||
const merged = this.mergeAllOf({
|
||||
oneOf: sub.oneOf.map((part: OpenAPISchema) => {
|
||||
return {
|
||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||
});
|
||||
|
||||
// each oneOf should be independent so exiting all the parent refs
|
||||
// otherwise it will cause false-positive recursive detection
|
||||
this.exitParents(merged);
|
||||
return merged;
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,14 +2,9 @@ import defaultTheme, { ResolvedThemeInterface, resolveTheme, ThemeInterface } fr
|
|||
import { querySelector } from '../utils/dom';
|
||||
import { isArray, isNumeric, mergeObjects } from '../utils/helpers';
|
||||
|
||||
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||
|
||||
export enum SideNavStyleEnum {
|
||||
SummaryOnly = 'summary-only',
|
||||
PathOnly = 'path-only',
|
||||
IdOnly = 'id-only',
|
||||
}
|
||||
import { setRedocLabels } from './Labels';
|
||||
import { SideNavStyleEnum } from './types';
|
||||
import type { LabelsConfigRaw, MDXComponentMeta } from './types';
|
||||
|
||||
export interface RedocRawOptions {
|
||||
theme?: ThemeInterface;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { bind } from 'decko';
|
|||
import * as EventEmitter from 'eventemitter3';
|
||||
|
||||
import { IS_BROWSER, querySelector, Throttle } from '../utils';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
const EVENT = 'scroll';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IS_BROWSER } from '../utils/';
|
||||
import { IMenuItem } from './MenuStore';
|
||||
import { OperationModel } from './models';
|
||||
import type { IMenuItem } from './types';
|
||||
import type { OperationModel } from './models';
|
||||
|
||||
import Worker from './SearchWorker.worker';
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as lunr from 'lunr';
|
||||
import type { SearchResult } from './types';
|
||||
|
||||
/* just for better typings */
|
||||
export default class Worker {
|
||||
|
@ -11,17 +12,6 @@ export default class Worker {
|
|||
fromExternalJS = fromExternalJS;
|
||||
}
|
||||
|
||||
export interface SearchDocument {
|
||||
title: string;
|
||||
description: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface SearchResult<T = string> {
|
||||
meta: T;
|
||||
score: number;
|
||||
}
|
||||
|
||||
let store: any[] = [];
|
||||
|
||||
lunr.tokenizer.separator = /\s+/;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types';
|
||||
import type { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types';
|
||||
|
||||
import { ContentItemModel, MenuBuilder } from './MenuBuilder';
|
||||
import { MenuBuilder } from './MenuBuilder';
|
||||
import { ApiInfoModel } from './models/ApiInfo';
|
||||
import { WebhookModel } from './models/Webhook';
|
||||
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
import type { ContentItemModel } from './types';
|
||||
/**
|
||||
* Store that contains all the specification related information in the form of tree
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { MarkdownRenderer, MDXComponentMeta } from '../MarkdownRenderer';
|
||||
import type { MDXComponentMeta } from '../types';
|
||||
import { MarkdownRenderer } from '../MarkdownRenderer';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
const TestComponent = () => null;
|
||||
|
|
|
@ -41,14 +41,14 @@ describe('Models', () => {
|
|||
expect(schema.title).toEqual('Foo');
|
||||
});
|
||||
|
||||
test('should merge oneOff to inside allOff', () => {
|
||||
test('should merge oneOf 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['x-parentRefs']).toHaveLength(1);
|
||||
expect(schema['x-parentRefs'][0]).toEqual('#/components/schemas/Ref');
|
||||
expect(schema.oneOf).toEqual([{ title: 'Bar' }, { title: 'Baz' }]);
|
||||
});
|
||||
|
||||
|
@ -60,7 +60,7 @@ describe('Models', () => {
|
|||
description: 'Overriden description',
|
||||
};
|
||||
|
||||
expect(parser.shallowDeref(schemaOrRef)).toMatchSnapshot();
|
||||
expect(parser.deref(schemaOrRef)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should correct resolve double $ref if no need sibling', () => {
|
||||
|
@ -70,7 +70,7 @@ describe('Models', () => {
|
|||
$ref: '#/components/schemas/Parent',
|
||||
};
|
||||
|
||||
expect(parser.deref(schemaOrRef, false, true)).toMatchSnapshot();
|
||||
expect(parser.deref(schemaOrRef, [], true)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,12 +2,17 @@
|
|||
|
||||
exports[`Models Schema should correct resolve double $ref if no need sibling 1`] = `
|
||||
Object {
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"type": "string",
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Parent",
|
||||
],
|
||||
"resolved": Object {
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -15,82 +20,80 @@ exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
|
|||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
"allOf": Array [
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"extra": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"password": Object {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
"username": Object {
|
||||
"description": "The user's name",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"extra": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"mobile": Object {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
"username": Object {
|
||||
"description": "The user's name",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"password": Object {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"mobile": Object {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
"allOf": Array [
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"email": Object {
|
||||
"description": "The user's email",
|
||||
"type": "string",
|
||||
},
|
||||
"extra": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"password": Object {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"email": Object {
|
||||
"description": "The user's email",
|
||||
"type": "string",
|
||||
},
|
||||
"extra": Object {
|
||||
"type": "string",
|
||||
},
|
||||
"mobile": Object {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"password": Object {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"mobile": Object {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -100,7 +103,12 @@ Object {
|
|||
|
||||
exports[`Models Schema should override description from $ref of the referenced component, when sibling description exists 1`] = `
|
||||
Object {
|
||||
"description": "Overriden description",
|
||||
"type": "object",
|
||||
"refsStack": Array [
|
||||
"#/components/schemas/Test",
|
||||
],
|
||||
"resolved": Object {
|
||||
"description": "Overriden description",
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
559
src/services/__tests__/models/Schema.circular.test.ts
Normal file
559
src/services/__tests__/models/Schema.circular.test.ts
Normal file
|
@ -0,0 +1,559 @@
|
|||
import outdent from 'outdent';
|
||||
import { parseYaml } from '@redocly/openapi-core';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { SchemaModel } from '../../models';
|
||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||
|
||||
import { circularDetailsPrinter, printSchema } from './helpers';
|
||||
|
||||
const opts = new RedocNormalizedOptions({}) as RedocNormalizedOptions;
|
||||
|
||||
describe('Models', () => {
|
||||
describe.only('Schema Circular tracking', () => {
|
||||
let parser;
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
test: val => typeof val === 'string',
|
||||
print: v => v as string,
|
||||
});
|
||||
|
||||
test('should detect circular for array nested in allOf', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Schema:
|
||||
type: object
|
||||
properties:
|
||||
a: { $ref: '#/components/schemas/Schema' }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Schema,
|
||||
'#/components/schemas/Schema',
|
||||
opts,
|
||||
);
|
||||
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||
`a: <object> !circular`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should not detect circular refs when ref used multiple times across allOf', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Foo:
|
||||
type: object
|
||||
properties:
|
||||
foo: { type: string }
|
||||
Schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Foo'
|
||||
- type: object
|
||||
properties:
|
||||
foobar: { $ref: '#/components/schemas/Foo' }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.Schema, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
foo: <string>
|
||||
foobar:
|
||||
foo: <string>
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect circular for array with self-reference', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Array:
|
||||
type: "array"
|
||||
items: { "$ref": "#/components/schemas/Array" }
|
||||
Schema:
|
||||
allOf: [{ "$ref": "#/components/schemas/Array" }]
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Schema,
|
||||
'#/components/schemas/Schema',
|
||||
opts,
|
||||
);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||
`[<array> !circular]`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should detect circular for object nested in allOf', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Object:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Object'
|
||||
- type: "object"
|
||||
properties: { "a": { "$ref": "#/components/schemas/Object" } }
|
||||
Schema:
|
||||
allOf: [{ "$ref": "#/components/schemas/Object" }]
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Schema,
|
||||
'#/components/schemas/Schema',
|
||||
opts,
|
||||
);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||
`a: <any> !circular`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should not detect circular for base DTO case', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
BaseDTO:
|
||||
type: object
|
||||
properties:
|
||||
id: {type: string}
|
||||
BaseB:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/BaseDTO'
|
||||
- type: object
|
||||
properties:
|
||||
fieldB: { type: string }
|
||||
BaseA:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/BaseDTO'
|
||||
- type: object
|
||||
properties:
|
||||
b: { $ref: '#/components/schemas/BaseB' }
|
||||
fieldA: { type: string }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.BaseA, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
id: <string>
|
||||
b:
|
||||
id: <string>
|
||||
fieldB: <string>
|
||||
fieldA: <string>
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect circular ref for self referencing discriminator', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
SelfComponentDto:
|
||||
type: object
|
||||
properties:
|
||||
self:
|
||||
type: object
|
||||
discriminator:
|
||||
propertyName: schemaId
|
||||
mapping:
|
||||
title: '#/components/schemas/SelfComponentDto'
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/SelfComponentDto'
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.SelfComponentDto, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
self: oneOf
|
||||
title ->
|
||||
self: oneOf
|
||||
title -> <object> !circular
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect circular with nested oneOf hoisting', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Node:
|
||||
type: 'object'
|
||||
allOf:
|
||||
- oneOf:
|
||||
- type: object
|
||||
properties:
|
||||
parent:
|
||||
$ref: '#/components/schemas/Node'
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.Node, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
oneOf
|
||||
object ->
|
||||
parent: oneOf
|
||||
object ->
|
||||
parent: <object> !circular
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect simple props recursion', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
PropRecursion:
|
||||
properties:
|
||||
children:
|
||||
type: object
|
||||
properties:
|
||||
a:
|
||||
$ref: '#/components/schemas/PropRecursion'
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.PropRecursion, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
children:
|
||||
a:
|
||||
children:
|
||||
a: <object> !circular
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect recursion for props with type array', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
PropsRecursion:
|
||||
properties:
|
||||
children:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PropsRecursion'
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.PropsRecursion, '', opts);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
children: [
|
||||
children: [<object> !circular]
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect and ignore allOf recursion', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Parent:
|
||||
$ref: '#/components/schemas/Child'
|
||||
Child:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Parent'
|
||||
- type: object
|
||||
properties:
|
||||
a:
|
||||
type: string
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Child,
|
||||
'#/components/schemas/Child',
|
||||
opts,
|
||||
);
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`a: <string>`);
|
||||
});
|
||||
|
||||
test('should detect and ignore allOf recursion in nested prop', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
ExternalReference:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/CompanyReference'
|
||||
- type: object
|
||||
properties:
|
||||
externalId: { type: string }
|
||||
CompanyReference:
|
||||
type: object
|
||||
required: [ guid, externalId ]
|
||||
properties:
|
||||
guid: { type: string }
|
||||
nestedRecursive: { $ref: '#/components/schemas/ExternalReference' }
|
||||
Entity:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ExternalReference'
|
||||
- type: object
|
||||
properties:
|
||||
directRecursive: { $ref: '#/components/schemas/ExternalReference' }
|
||||
selfRecursive: { $ref: '#/components/schemas/Entity' }
|
||||
anotherField: { $ref: '#/components/schemas/AnotherEntity' }
|
||||
AnotherEntity:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/CompanyReference'
|
||||
- type: object
|
||||
properties:
|
||||
someField: { type: number }
|
||||
anotherSelfRecursive: { $ref: '#/components/schemas/AnotherEntity' }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Entity,
|
||||
'#/components/schemas/Entity',
|
||||
opts,
|
||||
);
|
||||
|
||||
// TODO: this has a little issue with too early detection in anotherField -> nestedRecursive
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
guid*: <string>
|
||||
nestedRecursive: <object> !circular
|
||||
externalId*: <string>
|
||||
directRecursive:
|
||||
guid*: <string>
|
||||
nestedRecursive: <object> !circular
|
||||
externalId*: <string>
|
||||
selfRecursive: <object> !circular
|
||||
anotherField:
|
||||
guid*: <string>
|
||||
nestedRecursive: <object> !circular
|
||||
someField: <number>
|
||||
anotherSelfRecursive: <object> !circular
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect and ignore allOf with discriminator recursion', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Pet:
|
||||
type: object
|
||||
required: [ petType ]
|
||||
discriminator:
|
||||
propertyName: petType
|
||||
mapping:
|
||||
cat: '#/components/schemas/Cat'
|
||||
dog: '#/components/schemas/Dog'
|
||||
properties:
|
||||
category: { $ref: '#/components/schemas/Category' }
|
||||
status: { type: string }
|
||||
friend:
|
||||
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||
petType: { type: string }
|
||||
Cat:
|
||||
description: A representation of a cat
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
huntingSkill: { type: string }
|
||||
Dog:
|
||||
description: A representation of a dog
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
packSize: { type: integer }
|
||||
Category:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Pet,
|
||||
'#/components/schemas/Pet',
|
||||
opts,
|
||||
);
|
||||
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
oneOf
|
||||
cat ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
huntingSkill: <string>
|
||||
dog ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
packSize: <integer>
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect and recursion on the right level with array of discriminators', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
Pet:
|
||||
type: object
|
||||
required: [ petType ]
|
||||
discriminator:
|
||||
propertyName: petType
|
||||
mapping:
|
||||
cat: '#/components/schemas/Cat'
|
||||
dog: '#/components/schemas/Dog'
|
||||
properties:
|
||||
category: { $ref: '#/components/schemas/Category' }
|
||||
status: { type: string }
|
||||
friend:
|
||||
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||
petType: { type: string }
|
||||
Cat:
|
||||
description: A representation of a cat
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
huntingSkill: { type: string }
|
||||
Dog:
|
||||
description: A representation of a dog
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
packSize: { type: integer }
|
||||
Category:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
Response:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.Response,
|
||||
'#/components/schemas/Response',
|
||||
opts,
|
||||
);
|
||||
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
[
|
||||
oneOf
|
||||
cat ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
huntingSkill: <string>
|
||||
dog ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
packSize: <integer>
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should detect and recursion with discriminator and oneOf', () => {
|
||||
const spec = parseYaml(outdent`
|
||||
openapi: 3.0.0
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
pet:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
Pet:
|
||||
type: object
|
||||
required: [ petType ]
|
||||
discriminator:
|
||||
propertyName: petType
|
||||
mapping:
|
||||
cat: '#/components/schemas/Cat'
|
||||
dog: '#/components/schemas/Dog'
|
||||
properties:
|
||||
category: { $ref: '#/components/schemas/Category' }
|
||||
status: { type: string }
|
||||
friend:
|
||||
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||
petType: { type: string }
|
||||
Cat:
|
||||
description: A representation of a cat
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
huntingSkill: { type: string }
|
||||
Dog:
|
||||
description: A representation of a dog
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
packSize: { type: integer }
|
||||
Category:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
`) as any;
|
||||
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
spec.components.schemas.User,
|
||||
'#/components/schemas/User',
|
||||
opts,
|
||||
);
|
||||
|
||||
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||
pet: oneOf
|
||||
Pet -> oneOf
|
||||
cat ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
huntingSkill: <string>
|
||||
dog ->
|
||||
category:
|
||||
name: <string>
|
||||
status: <string>
|
||||
friend: <object> !circular
|
||||
petType*: <string>
|
||||
packSize: <integer>
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,20 +4,21 @@ exports[`Models Schema schemaDefinition should resolve field with conditional op
|
|||
Object {
|
||||
"allOf": undefined,
|
||||
"default": undefined,
|
||||
"format": undefined,
|
||||
"items": Object {
|
||||
"allOf": undefined,
|
||||
"format": "url",
|
||||
"parentRefs": Array [],
|
||||
"title": undefined,
|
||||
"type": "string",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
},
|
||||
"maxItems": 20,
|
||||
"minItems": 1,
|
||||
"parentRefs": Array [],
|
||||
"title": "isString",
|
||||
"type": "string",
|
||||
"x-circular-ref": undefined,
|
||||
"x-displayName": "isString",
|
||||
"x-parentRefs": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -25,17 +26,16 @@ exports[`Models Schema schemaDefinition should resolve field with conditional op
|
|||
Object {
|
||||
"allOf": undefined,
|
||||
"default": undefined,
|
||||
"format": undefined,
|
||||
"items": Object {
|
||||
"allOf": undefined,
|
||||
"format": "url",
|
||||
"parentRefs": Array [],
|
||||
"title": undefined,
|
||||
"type": "string",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
},
|
||||
"maxItems": 10,
|
||||
"minItems": 1,
|
||||
"parentRefs": Array [],
|
||||
"pattern": "\\\\d+",
|
||||
"title": "notString",
|
||||
"type": Array [
|
||||
|
@ -43,16 +43,16 @@ Object {
|
|||
"integer",
|
||||
"null",
|
||||
],
|
||||
"x-circular-ref": undefined,
|
||||
"x-displayName": "notString",
|
||||
"x-parentRefs": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = `
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"format": undefined,
|
||||
"maxItems": 2,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"allOf": undefined,
|
||||
|
@ -60,36 +60,40 @@ Object {
|
|||
"enum": Array [
|
||||
10,
|
||||
],
|
||||
"format": undefined,
|
||||
"items": Object {
|
||||
"allOf": undefined,
|
||||
"format": "url",
|
||||
"parentRefs": Array [],
|
||||
"title": undefined,
|
||||
"type": "string",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
},
|
||||
"maxItems": 20,
|
||||
"minItems": 1,
|
||||
"parentRefs": Array [],
|
||||
"title": undefined,
|
||||
"type": Array [
|
||||
"string",
|
||||
"integer",
|
||||
"null",
|
||||
],
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-refsStack": Array [
|
||||
"/oneOf/0",
|
||||
],
|
||||
},
|
||||
},
|
||||
"title": "=== 10",
|
||||
"type": "object",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = `
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
"format": undefined,
|
||||
"maxItems": 20,
|
||||
"parentRefs": Array [],
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"description": "The list of URL to a cute photos featuring pet",
|
||||
|
@ -104,9 +108,14 @@ Object {
|
|||
"integer",
|
||||
"null",
|
||||
],
|
||||
"x-refsStack": Array [
|
||||
"/oneOf/1",
|
||||
],
|
||||
},
|
||||
},
|
||||
"title": "case 2",
|
||||
"type": "object",
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
}
|
||||
`;
|
||||
|
|
76
src/services/__tests__/models/helpers.ts
Normal file
76
src/services/__tests__/models/helpers.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import type { SchemaModel } from '../../models';
|
||||
|
||||
function printType(type: string | string[]): string {
|
||||
return `<${type}>`;
|
||||
}
|
||||
|
||||
function printDescription(description: string | string[]): string {
|
||||
return description ? ` (${description})` : '';
|
||||
}
|
||||
|
||||
export function circularDetailsPrinter(schema: SchemaModel): string {
|
||||
return schema.isCircular ? ' !circular' : '';
|
||||
}
|
||||
|
||||
export function printSchema(
|
||||
schema: SchemaModel,
|
||||
detailsPrinter: (schema: SchemaModel) => string = () => '',
|
||||
identLevel = 0,
|
||||
inline = false,
|
||||
): string {
|
||||
if (!schema) return '';
|
||||
const ident = ' '.repeat(identLevel);
|
||||
|
||||
if (schema.isPrimitive || schema.isCircular) {
|
||||
if (schema.type === 'array' && schema.items) {
|
||||
return `${inline ? ' ' : ident}[${printType(schema.items.type)}${detailsPrinter(
|
||||
schema.items,
|
||||
)}]${printDescription(schema.items.description)}`;
|
||||
} else {
|
||||
return `${inline ? ' ' : ident}${printType(schema.displayType)}${detailsPrinter(
|
||||
schema,
|
||||
)}${printDescription(schema.description)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.oneOf) {
|
||||
return (
|
||||
`${inline ? ' ' : ident}oneOf\n` +
|
||||
schema.oneOf
|
||||
.map(sub => {
|
||||
return (
|
||||
`${ident} ${sub.title || sub.displayType} ->` +
|
||||
printSchema(sub, detailsPrinter, identLevel + 2, true)
|
||||
);
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.fields) {
|
||||
const prefix = inline ? '\n' : '';
|
||||
return (
|
||||
prefix +
|
||||
schema.fields
|
||||
.map(f => {
|
||||
return `${ident}${f.name}${f.required ? '*' : ''}:${printSchema(
|
||||
f.schema,
|
||||
detailsPrinter,
|
||||
identLevel + 1,
|
||||
true,
|
||||
)}`;
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.items) {
|
||||
return (
|
||||
`${inline ? ' ' : ident}[\n` +
|
||||
printSchema(schema.items, detailsPrinter, identLevel) +
|
||||
`\n${inline ? ident.slice(0, -2) : ident}]`
|
||||
);
|
||||
}
|
||||
|
||||
return ' error';
|
||||
}
|
|
@ -11,3 +11,4 @@ export * from './RedocNormalizedOptions';
|
|||
export * from './MenuBuilder';
|
||||
export * from './SearchStore';
|
||||
export * from './MarkerService';
|
||||
export * from './types';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
|
||||
import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
|
||||
import { IS_BROWSER } from '../../utils/';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
export class ApiInfoModel implements OpenAPIInfo {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { OpenAPICallback, Referenced } from '../../types';
|
||||
import { isOperationName, JsonPointer } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { OperationModel } from './Operation';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPICallback, Referenced } from '../../types';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
export class CallbackModel {
|
||||
@observable
|
||||
|
@ -23,8 +23,7 @@ export class CallbackModel {
|
|||
makeObservable(this);
|
||||
|
||||
this.name = name;
|
||||
const paths = parser.deref<OpenAPICallback>(infoOrRef);
|
||||
parser.exitRef(infoOrRef);
|
||||
const { resolved: paths } = parser.deref<OpenAPICallback>(infoOrRef);
|
||||
|
||||
for (const pathName of Object.keys(paths)) {
|
||||
const path = paths[pathName];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAPIEncoding, OpenAPIExample, Referenced } from '../../types';
|
||||
import type { OpenAPIEncoding, OpenAPIExample, Referenced } from '../../types';
|
||||
import { isFormUrlEncoded, isJsonLike, urlFormEncodePayload } from '../../utils/openapi';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
|
||||
const externalExamplesCache: { [url: string]: Promise<any> } = {};
|
||||
|
||||
|
@ -16,14 +16,13 @@ export class ExampleModel {
|
|||
public mime: string,
|
||||
encoding?: { [field: string]: OpenAPIEncoding },
|
||||
) {
|
||||
const example = parser.deref(infoOrRef);
|
||||
const { resolved: example } = parser.deref(infoOrRef);
|
||||
this.value = example.value;
|
||||
this.summary = example.summary;
|
||||
this.description = example.description;
|
||||
if (example.externalValue) {
|
||||
this.externalValueUrl = new URL(example.externalValue, parser.specUrl).href;
|
||||
}
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
if (isFormUrlEncoded(mime) && this.value && typeof this.value === 'object') {
|
||||
this.value = urlFormEncodePayload(this.value, encoding);
|
||||
|
@ -35,7 +34,7 @@ export class ExampleModel {
|
|||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (externalExamplesCache[this.externalValueUrl]) {
|
||||
if (this.externalValueUrl in externalExamplesCache) {
|
||||
return externalExamplesCache[this.externalValueUrl];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import {
|
||||
import type {
|
||||
OpenAPIParameter,
|
||||
OpenAPIParameterLocation,
|
||||
OpenAPIParameterStyle,
|
||||
Referenced,
|
||||
} from '../../types';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
import { extractExtensions } from '../../utils/openapi';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { SchemaModel } from './Schema';
|
||||
import { ExampleModel } from './Example';
|
||||
import { isArray, mapValues } from '../../utils/helpers';
|
||||
|
@ -64,10 +64,11 @@ export class FieldModel {
|
|||
infoOrRef: Referenced<OpenAPIParameter> & { name?: string; kind?: string },
|
||||
pointer: string,
|
||||
options: RedocNormalizedOptions,
|
||||
refsStack?: string[],
|
||||
) {
|
||||
makeObservable(this);
|
||||
|
||||
const info = parser.deref<OpenAPIParameter>(infoOrRef);
|
||||
const { resolved: info } = parser.deref<OpenAPIParameter>(infoOrRef);
|
||||
this.kind = infoOrRef.kind || 'field';
|
||||
this.name = infoOrRef.name || info.name;
|
||||
this.in = info.in;
|
||||
|
@ -80,7 +81,7 @@ export class FieldModel {
|
|||
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
||||
}
|
||||
|
||||
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options);
|
||||
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options, false, refsStack);
|
||||
this.description =
|
||||
info.description === undefined ? this.schema.description || '' : info.description;
|
||||
this.example = info.example || this.schema.example;
|
||||
|
@ -110,7 +111,6 @@ export class FieldModel {
|
|||
}
|
||||
|
||||
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(info, options.showExtensions);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
||||
import type { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
||||
import { safeSlugify } from '../../utils';
|
||||
import { MarkdownHeading, MarkdownRenderer } from '../MarkdownRenderer';
|
||||
import { ContentItemModel } from '../MenuBuilder';
|
||||
import { IMenuItem, MenuItemGroupType } from '../MenuStore';
|
||||
import { MarkdownRenderer } from '../MarkdownRenderer';
|
||||
import type { ContentItemModel, IMenuItem, MarkdownHeading, MenuItemGroupType } from '../types';
|
||||
|
||||
/**
|
||||
* Operations Group model ready to be used by components
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { action, computed, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { OpenAPIMediaType } from '../../types';
|
||||
import type { OpenAPIMediaType } from '../../types';
|
||||
import { MediaTypeModel } from './MediaType';
|
||||
|
||||
import { mergeSimilarMediaTypes } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
/**
|
||||
* MediaContent model ready to be sued by React components
|
||||
|
@ -34,7 +34,6 @@ export class MediaContentModel {
|
|||
this.mediaTypes = Object.keys(info).map(name => {
|
||||
const mime = info[name];
|
||||
// reset deref cache just in case something is left there
|
||||
parser.resetVisited();
|
||||
return new MediaTypeModel(parser, name, isRequestType, mime, options);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as Sampler from 'openapi-sampler';
|
||||
|
||||
import { OpenAPIMediaType } from '../../types';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIMediaType } from '../../types';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { SchemaModel } from './Schema';
|
||||
|
||||
import { isJsonLike, mapValues } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { ExampleModel } from './Example';
|
||||
|
||||
export class MediaTypeModel {
|
||||
|
@ -40,7 +40,7 @@ export class MediaTypeModel {
|
|||
this.examples = {
|
||||
default: new ExampleModel(
|
||||
parser,
|
||||
{ value: parser.shallowDeref(info.example) },
|
||||
{ value: parser.deref(info.example).resolved },
|
||||
name,
|
||||
info.encoding,
|
||||
),
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { IMenuItem } from '../MenuStore';
|
||||
import { GroupModel } from './Group.model';
|
||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||
|
||||
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||
|
||||
import {
|
||||
extractExtensions,
|
||||
getOperationSummary,
|
||||
|
@ -17,15 +11,20 @@ import {
|
|||
sortByField,
|
||||
sortByRequired,
|
||||
} from '../../utils';
|
||||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
import { GroupModel } from './Group.model';
|
||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||
import { CallbackModel } from './Callback';
|
||||
import { FieldModel } from './Field';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
import { RequestBodyModel } from './RequestBody';
|
||||
import { ResponseModel } from './Response';
|
||||
import { SideNavStyleEnum } from '../RedocNormalizedOptions';
|
||||
import { SideNavStyleEnum } from '../types';
|
||||
|
||||
import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { MediaContentModel } from './MediaContent';
|
||||
import type { ContentItemModel, ExtendedOpenAPIOperation, IMenuItem } from '../types';
|
||||
|
||||
export interface XPayloadSample {
|
||||
lang: 'payload';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { OpenAPIRequestBody, Referenced } from '../../types';
|
||||
import type { OpenAPIRequestBody, Referenced } from '../../types';
|
||||
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
import { getContentWithLegacyExamples } from '../../utils';
|
||||
|
||||
|
@ -19,10 +19,9 @@ export class RequestBodyModel {
|
|||
|
||||
constructor({ parser, infoOrRef, options, isEvent }: RequestBodyProps) {
|
||||
const isRequest = !isEvent;
|
||||
const info = parser.deref(infoOrRef);
|
||||
const { resolved: info } = parser.deref(infoOrRef);
|
||||
this.description = info.description || '';
|
||||
this.required = !!info.required;
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
const mediaContent = getContentWithLegacyExamples(info);
|
||||
if (mediaContent !== undefined) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { OpenAPIResponse, Referenced } from '../../types';
|
||||
import type { OpenAPIResponse, Referenced } from '../../types';
|
||||
|
||||
import { getStatusCodeType, extractExtensions } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { FieldModel } from './Field';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
|
||||
|
@ -41,8 +41,7 @@ export class ResponseModel {
|
|||
|
||||
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
||||
|
||||
const info = parser.deref(infoOrRef);
|
||||
parser.exitRef(infoOrRef);
|
||||
const { resolved: info } = parser.deref(infoOrRef);
|
||||
this.code = code;
|
||||
if (info.content !== undefined) {
|
||||
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { action, observable, makeObservable } from 'mobx';
|
||||
|
||||
import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types';
|
||||
import type { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types';
|
||||
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { pushRef } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { FieldModel } from './Field';
|
||||
|
||||
import { MergedOpenAPISchema } from '../';
|
||||
import { MergedOpenAPISchema } from '../types';
|
||||
import {
|
||||
detectType,
|
||||
extractExtensions,
|
||||
|
@ -80,18 +81,19 @@ export class SchemaModel {
|
|||
pointer: string,
|
||||
private options: RedocNormalizedOptions,
|
||||
isChild: boolean = false,
|
||||
private refsStack: string[] = [],
|
||||
) {
|
||||
makeObservable(this);
|
||||
|
||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||
this.rawSchema = parser.deref(schemaOrRef, false, true);
|
||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
||||
|
||||
const { resolved, refsStack: newRefsStack } = parser.deref(schemaOrRef, refsStack, true);
|
||||
this.refsStack = pushRef(newRefsStack, this.pointer);
|
||||
this.rawSchema = resolved;
|
||||
|
||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, this.refsStack);
|
||||
this.init(parser, isChild);
|
||||
|
||||
parser.exitRef(schemaOrRef);
|
||||
parser.exitParents(this.schema);
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(this.schema, options.showExtensions);
|
||||
}
|
||||
|
@ -112,7 +114,7 @@ export class SchemaModel {
|
|||
|
||||
init(parser: OpenAPIParser, isChild: boolean) {
|
||||
const schema = this.schema;
|
||||
this.isCircular = schema['x-circular-ref'];
|
||||
this.isCircular = !!schema['x-circular-ref'];
|
||||
|
||||
this.title =
|
||||
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
||||
|
@ -189,16 +191,18 @@ export class SchemaModel {
|
|||
}
|
||||
|
||||
if (this.hasType('object')) {
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options, this.refsStack);
|
||||
} else if (this.hasType('array')) {
|
||||
if (isArray(schema.items) || isArray(schema.prefixItems)) {
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options, this.refsStack);
|
||||
} else if (isObject(schema.items)) {
|
||||
this.items = new SchemaModel(
|
||||
parser,
|
||||
schema.items as OpenAPISchema,
|
||||
this.pointer + '/items',
|
||||
this.options,
|
||||
false,
|
||||
this.refsStack,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -231,9 +235,9 @@ export class SchemaModel {
|
|||
|
||||
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
||||
this.oneOf = oneOf!.map((variant, idx) => {
|
||||
const derefVariant = parser.deref(variant, false, true);
|
||||
const { resolved: derefVariant, refsStack } = parser.deref(variant, this.refsStack, true);
|
||||
|
||||
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
|
||||
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx, refsStack);
|
||||
|
||||
// try to infer title
|
||||
const title =
|
||||
|
@ -252,13 +256,10 @@ export class SchemaModel {
|
|||
} as OpenAPISchema,
|
||||
this.pointer + '/oneOf/' + idx,
|
||||
this.options,
|
||||
false,
|
||||
this.refsStack,
|
||||
);
|
||||
|
||||
parser.exitRef(variant);
|
||||
// each oneOf should be independent so exiting all the parent refs
|
||||
// otherwise it will cause false-positive recursive detection
|
||||
parser.exitParents(merged);
|
||||
|
||||
return schema;
|
||||
});
|
||||
|
||||
|
@ -280,16 +281,11 @@ export class SchemaModel {
|
|||
}
|
||||
}
|
||||
|
||||
private initDiscriminator(
|
||||
schema: OpenAPISchema & {
|
||||
parentRefs?: string[];
|
||||
},
|
||||
parser: OpenAPIParser,
|
||||
) {
|
||||
private initDiscriminator(schema: OpenAPISchema, parser: OpenAPIParser) {
|
||||
const discriminator = getDiscriminator(schema)!;
|
||||
this.discriminatorProp = discriminator.propertyName;
|
||||
const implicitInversedMapping = parser.findDerived([
|
||||
...(schema.parentRefs || []),
|
||||
...(this.schema['x-parentRefs'] || []),
|
||||
this.pointer,
|
||||
]);
|
||||
|
||||
|
@ -372,7 +368,14 @@ export class SchemaModel {
|
|||
}
|
||||
|
||||
this.oneOf = refs.map(({ $ref, name }) => {
|
||||
const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true);
|
||||
const innerSchema = new SchemaModel(
|
||||
parser,
|
||||
{ $ref },
|
||||
$ref,
|
||||
this.options,
|
||||
true,
|
||||
this.refsStack.slice(0, -1),
|
||||
);
|
||||
innerSchema.title = name;
|
||||
return innerSchema;
|
||||
});
|
||||
|
@ -405,6 +408,8 @@ export class SchemaModel {
|
|||
} as OpenAPISchema,
|
||||
this.pointer + '/oneOf/' + idx,
|
||||
this.options,
|
||||
false,
|
||||
this.refsStack,
|
||||
),
|
||||
);
|
||||
this.oneOfType = 'One of';
|
||||
|
@ -416,6 +421,7 @@ function buildFields(
|
|||
schema: OpenAPISchema,
|
||||
$ref: string,
|
||||
options: RedocNormalizedOptions,
|
||||
refsStack: string[],
|
||||
): FieldModel[] {
|
||||
const props = schema.properties || schema.prefixItems || schema.items || {};
|
||||
const patternProps = schema.patternProperties || {};
|
||||
|
@ -447,6 +453,7 @@ function buildFields(
|
|||
},
|
||||
$ref + '/properties/' + fieldName,
|
||||
options,
|
||||
refsStack,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -479,6 +486,7 @@ function buildFields(
|
|||
},
|
||||
`${$ref}/patternProperties/${fieldName}`,
|
||||
options,
|
||||
refsStack,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
@ -498,6 +506,7 @@ function buildFields(
|
|||
},
|
||||
$ref + '/additionalProperties',
|
||||
options,
|
||||
refsStack,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -509,6 +518,7 @@ function buildFields(
|
|||
fieldsCount: fields.length,
|
||||
$ref,
|
||||
options,
|
||||
refsStack,
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -521,12 +531,14 @@ function buildAdditionalItems({
|
|||
fieldsCount,
|
||||
$ref,
|
||||
options,
|
||||
refsStack,
|
||||
}: {
|
||||
parser: OpenAPIParser;
|
||||
schema?: OpenAPISchema | OpenAPISchema[] | boolean;
|
||||
fieldsCount: number;
|
||||
$ref: string;
|
||||
options: RedocNormalizedOptions;
|
||||
refsStack: string[];
|
||||
}) {
|
||||
if (isBoolean(schema)) {
|
||||
return schema
|
||||
|
@ -539,6 +551,7 @@ function buildAdditionalItems({
|
|||
},
|
||||
`${$ref}/additionalItems`,
|
||||
options,
|
||||
refsStack,
|
||||
),
|
||||
]
|
||||
: [];
|
||||
|
@ -556,6 +569,7 @@ function buildAdditionalItems({
|
|||
},
|
||||
`${$ref}/additionalItems`,
|
||||
options,
|
||||
refsStack,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
@ -571,6 +585,7 @@ function buildAdditionalItems({
|
|||
},
|
||||
`${$ref}/additionalItems`,
|
||||
options,
|
||||
refsStack,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
|
||||
export interface SecurityScheme extends OpenAPISecurityScheme {
|
||||
id: string;
|
||||
|
@ -16,7 +16,7 @@ export class SecurityRequirementModel {
|
|||
|
||||
this.schemes = Object.keys(requirement || {})
|
||||
.map(id => {
|
||||
const scheme = parser.deref(schemes[id]);
|
||||
const { resolved: scheme } = parser.deref(schemes[id]);
|
||||
const scopes = requirement[id] || [];
|
||||
|
||||
if (!scheme) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAPISecurityScheme, Referenced } from '../../types';
|
||||
import type { OpenAPISecurityScheme, Referenced } from '../../types';
|
||||
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
|
||||
export class SecuritySchemeModel {
|
||||
id: string;
|
||||
|
@ -24,7 +24,7 @@ export class SecuritySchemeModel {
|
|||
};
|
||||
|
||||
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
||||
const info = parser.deref(scheme);
|
||||
const { resolved: info } = parser.deref(scheme);
|
||||
this.id = id;
|
||||
this.sectionId = SECURITY_SCHEMES_SECTION_PREFIX + id;
|
||||
this.type = info.type;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { OpenAPIPath, Referenced } from '../../types';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { OpenAPIPath, Referenced } from '../../types';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { OperationModel } from './Operation';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { isOperationName } from '../..';
|
||||
|
||||
export class WebhookModel {
|
||||
|
@ -12,8 +12,7 @@ export class WebhookModel {
|
|||
options: RedocNormalizedOptions,
|
||||
infoOrRef?: Referenced<OpenAPIPath>,
|
||||
) {
|
||||
const webhooks = parser.deref<OpenAPIPath>(infoOrRef || {});
|
||||
parser.exitRef(infoOrRef);
|
||||
const { resolved: webhooks } = parser.deref<OpenAPIPath>(infoOrRef || {});
|
||||
this.initWebhooks(parser, webhooks, options);
|
||||
}
|
||||
|
||||
|
|
131
src/services/types.ts
Normal file
131
src/services/types.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import {
|
||||
OpenAPIOperation,
|
||||
OpenAPIParameter,
|
||||
OpenAPISchema,
|
||||
OpenAPIServer,
|
||||
OpenAPITag,
|
||||
Referenced,
|
||||
} from '../types';
|
||||
import { AppStore } from './AppStore';
|
||||
import { GroupModel } from './models';
|
||||
import { OperationModel } from './models/Operation';
|
||||
import { RedocRawOptions } from './RedocNormalizedOptions';
|
||||
|
||||
export interface StoreState {
|
||||
menu: {
|
||||
activeItemIdx: number;
|
||||
};
|
||||
spec: {
|
||||
url?: string;
|
||||
data: any;
|
||||
};
|
||||
searchIndex: any;
|
||||
options: RedocRawOptions;
|
||||
}
|
||||
|
||||
export interface LabelsConfig {
|
||||
enum: string;
|
||||
enumSingleValue: string;
|
||||
enumArray: string;
|
||||
default: string;
|
||||
deprecated: string;
|
||||
example: string;
|
||||
examples: string;
|
||||
recursive: string;
|
||||
arrayOf: string;
|
||||
webhook: string;
|
||||
const: string;
|
||||
noResultsFound: string;
|
||||
download: string;
|
||||
downloadSpecification: string;
|
||||
responses: string;
|
||||
callbackResponses: string;
|
||||
requestSamples: string;
|
||||
responseSamples: string;
|
||||
}
|
||||
|
||||
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
||||
|
||||
export interface MDXComponentMeta {
|
||||
component: React.ComponentType;
|
||||
propsSelector: (store?: AppStore) => any;
|
||||
props?: object;
|
||||
}
|
||||
|
||||
export interface MarkdownHeading {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
items?: MarkdownHeading[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type ContentItemModel = GroupModel | OperationModel;
|
||||
|
||||
export type TagInfo = OpenAPITag & {
|
||||
operations: ExtendedOpenAPIOperation[];
|
||||
used?: boolean;
|
||||
};
|
||||
|
||||
export type ExtendedOpenAPIOperation = {
|
||||
pointer: string;
|
||||
pathName: string;
|
||||
httpVerb: string;
|
||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||
pathServers: Array<OpenAPIServer> | undefined;
|
||||
isWebhook: boolean;
|
||||
} & OpenAPIOperation;
|
||||
|
||||
export type TagsInfoMap = Record<string, TagInfo>;
|
||||
|
||||
export interface TagGroup {
|
||||
name: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||
export type MenuItemType = MenuItemGroupType | 'operation';
|
||||
|
||||
export interface IMenuItem {
|
||||
id: string;
|
||||
absoluteIdx?: number;
|
||||
name: string;
|
||||
sidebarLabel: string;
|
||||
description?: string;
|
||||
depth: number;
|
||||
active: boolean;
|
||||
expanded: boolean;
|
||||
items: IMenuItem[];
|
||||
parent?: IMenuItem;
|
||||
deprecated?: boolean;
|
||||
type: MenuItemType;
|
||||
|
||||
deactivate(): void;
|
||||
activate(): void;
|
||||
|
||||
collapse(): void;
|
||||
expand(): void;
|
||||
}
|
||||
|
||||
export interface SearchDocument {
|
||||
title: string;
|
||||
description: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface SearchResult<T = string> {
|
||||
meta: T;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export enum SideNavStyleEnum {
|
||||
SummaryOnly = 'summary-only',
|
||||
PathOnly = 'path-only',
|
||||
IdOnly = 'id-only',
|
||||
}
|
||||
|
||||
export type MergedOpenAPISchema = OpenAPISchema & {
|
||||
'x-refsStack'?: string[];
|
||||
'x-parentRefs'?: string[];
|
||||
'x-circular-ref'?: boolean;
|
||||
};
|
|
@ -3,9 +3,10 @@ import { hydrate as hydrateComponent, render } from 'react-dom';
|
|||
import { configure } from 'mobx';
|
||||
|
||||
import { Redoc, RedocStandalone } from './components/';
|
||||
import { AppStore, StoreState } from './services/AppStore';
|
||||
import { AppStore } from './services/AppStore';
|
||||
import { debugTime, debugTimeEnd } from './utils/debug';
|
||||
import { querySelector } from './utils/dom';
|
||||
import type { StoreState } from './services';
|
||||
|
||||
configure({
|
||||
useProxies: 'ifavailable',
|
||||
|
|
|
@ -40,7 +40,10 @@ export interface OpenAPIPaths {
|
|||
[path: string]: OpenAPIPath;
|
||||
}
|
||||
export interface OpenAPIRef {
|
||||
'x-refsStack'?: string[];
|
||||
$ref: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type Referenced<T> = OpenAPIRef | T;
|
||||
|
|
|
@ -121,6 +121,10 @@ export function isPrimitiveType(
|
|||
schema: OpenAPISchema,
|
||||
type: string | string[] | undefined = schema.type,
|
||||
) {
|
||||
if (schema['x-circular-ref']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
@ -552,13 +556,13 @@ export function mergeParams(
|
|||
): Array<Referenced<OpenAPIParameter>> {
|
||||
const operationParamNames = {};
|
||||
operationParams.forEach(param => {
|
||||
param = parser.shallowDeref(param);
|
||||
({ resolved: param } = parser.deref(param));
|
||||
operationParamNames[param.name + '_' + param.in] = true;
|
||||
});
|
||||
|
||||
// filter out path params overridden by operation ones with the same name
|
||||
pathParams = pathParams.filter(param => {
|
||||
param = parser.shallowDeref(param);
|
||||
({ resolved: param } = parser.deref(param));
|
||||
return !operationParamNames[param.name + '_' + param.in];
|
||||
});
|
||||
|
||||
|
@ -643,6 +647,8 @@ export const shortenHTTPVerb = verb =>
|
|||
export function isRedocExtension(key: string): boolean {
|
||||
const redocExtensions = {
|
||||
'x-circular-ref': true,
|
||||
'x-parentRefs': true,
|
||||
'x-refsStack': true,
|
||||
'x-code-samples': true, // deprecated
|
||||
'x-codeSamples': true,
|
||||
'x-displayName': true,
|
||||
|
|
Loading…
Reference in New Issue
Block a user