docusaurus-theme-openapi-docs 5.1.0 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/theme/Schema/index.js +93 -173
- package/lib/theme/Schema/normalize.d.ts +41 -0
- package/lib/theme/Schema/normalize.js +210 -0
- package/lib/theme/Schema/normalize.test.d.ts +1 -0
- package/lib/theme/Schema/normalize.test.js +339 -0
- package/package.json +3 -3
- package/src/theme/Schema/index.tsx +99 -187
- package/src/theme/Schema/normalize.test.ts +360 -0
- package/src/theme/Schema/normalize.ts +224 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
|
-
import React, { useCallback } from "react";
|
|
8
|
+
import React, { useCallback, useMemo } from "react";
|
|
9
9
|
|
|
10
10
|
import { translate } from "@docusaurus/Translate";
|
|
11
11
|
import { setSchemaSelection } from "@theme/ApiExplorer/SchemaSelection/slice";
|
|
@@ -14,6 +14,14 @@ import { ClosingArrayBracket, OpeningArrayBracket } from "@theme/ArrayBrackets";
|
|
|
14
14
|
import Details from "@theme/Details";
|
|
15
15
|
import DiscriminatorTabs from "@theme/DiscriminatorTabs";
|
|
16
16
|
import Markdown from "@theme/Markdown";
|
|
17
|
+
import {
|
|
18
|
+
findPropertyDeep,
|
|
19
|
+
foldSiblingsIntoBranches,
|
|
20
|
+
getDiscriminator,
|
|
21
|
+
isCircularMarker,
|
|
22
|
+
mergeAllOf,
|
|
23
|
+
normalizeSchema,
|
|
24
|
+
} from "@theme/Schema/normalize";
|
|
17
25
|
import {
|
|
18
26
|
SchemaDepthProvider,
|
|
19
27
|
useSchemaDepth,
|
|
@@ -22,192 +30,12 @@ import {
|
|
|
22
30
|
import SchemaItem from "@theme/SchemaItem";
|
|
23
31
|
import SchemaTabs from "@theme/SchemaTabs";
|
|
24
32
|
import TabItem from "@theme/TabItem";
|
|
25
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
26
|
-
import { merge } from "allof-merge";
|
|
27
33
|
import clsx from "clsx";
|
|
28
34
|
import isEmpty from "lodash/isEmpty";
|
|
29
35
|
|
|
30
36
|
import { getQualifierMessage, getSchemaName } from "../../markdown/schema";
|
|
31
37
|
import type { SchemaObject } from "../../types.d";
|
|
32
38
|
|
|
33
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
34
|
-
// const jsonSchemaMergeAllOf = require("json-schema-merge-allof");
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Strip `additionalProperties: false` from sibling allOf members so the
|
|
38
|
-
* strict-AND semantics of `allof-merge` don't collapse the result to an
|
|
39
|
-
* unsatisfiable empty schema.
|
|
40
|
-
*
|
|
41
|
-
* Per JSON Schema, two allOf members that each set `additionalProperties: false`
|
|
42
|
-
* with disjoint `properties` sets define an unsatisfiable schema (no value can
|
|
43
|
-
* satisfy both — each member rejects the other's properties). `allof-merge` is
|
|
44
|
-
* technically correct to drop all properties in that case, but it leaves the
|
|
45
|
-
* rendered schema blank.
|
|
46
|
-
*
|
|
47
|
-
* NSwag and Swashbuckle emit this pattern by default whenever a model uses
|
|
48
|
-
* inheritance/composition, so it's the dominant style for .NET-generated specs.
|
|
49
|
-
* Redoc, Swagger UI, and Stoplight all union the properties and ignore the
|
|
50
|
-
* conflicting flag — the approach this helper emulates by stripping the flag
|
|
51
|
-
* before delegating to `allof-merge`. The flag is render-irrelevant anyway:
|
|
52
|
-
* `additionalProperties: false` is treated identically to `undefined` by the
|
|
53
|
-
* AdditionalProperties component below (line ~641).
|
|
54
|
-
*
|
|
55
|
-
* Strips from every allOf member whenever the parent has ≥2 members. The
|
|
56
|
-
* collapse triggers symmetrically (both siblings strict) or asymmetrically
|
|
57
|
-
* (one strict member rejects another's properties as "additional"), so the
|
|
58
|
-
* presence of multiple members is the right gate. Single-member allOf is left
|
|
59
|
-
* alone — it can't conflict with anything.
|
|
60
|
-
*
|
|
61
|
-
* See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1119
|
|
62
|
-
* Mirrored in plugin: docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
|
|
63
|
-
*/
|
|
64
|
-
const stripConflictingAdditionalProps = (node: any): any => {
|
|
65
|
-
if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
|
|
66
|
-
if (!node || typeof node !== "object") return node;
|
|
67
|
-
|
|
68
|
-
let working: any = node;
|
|
69
|
-
if (Array.isArray(node.allOf) && node.allOf.length > 1) {
|
|
70
|
-
const hasStrictMember = node.allOf.some(
|
|
71
|
-
(m: any) => m && m.additionalProperties === false
|
|
72
|
-
);
|
|
73
|
-
if (hasStrictMember) {
|
|
74
|
-
working = {
|
|
75
|
-
...node,
|
|
76
|
-
allOf: node.allOf.map((m: any) => {
|
|
77
|
-
if (m && m.additionalProperties === false) {
|
|
78
|
-
const { additionalProperties: _drop, ...rest } = m;
|
|
79
|
-
return rest;
|
|
80
|
-
}
|
|
81
|
-
return m;
|
|
82
|
-
}),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const result: any = {};
|
|
88
|
-
for (const [k, v] of Object.entries(working)) {
|
|
89
|
-
result[k] = stripConflictingAdditionalProps(v);
|
|
90
|
-
}
|
|
91
|
-
return result;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const mergeAllOf = (allOf: any) => {
|
|
95
|
-
const onMergeError = (msg: string) => {
|
|
96
|
-
console.warn(msg);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const mergedSchemas = merge(stripConflictingAdditionalProps(allOf), {
|
|
100
|
-
onMergeError,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return mergedSchemas ?? {};
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Fold sibling fields into each `oneOf`/`anyOf` branch via allOf-merge, so each
|
|
108
|
-
* branch is self-contained. Mirrors Redoc's `SchemaModel.initOneOf` behavior.
|
|
109
|
-
* Without this, when an `allOf` override redefines a nested property with
|
|
110
|
-
* `oneOf`, the merged schema ends up with both `properties` and `oneOf` as
|
|
111
|
-
* siblings — and the renderer prints the shared properties twice.
|
|
112
|
-
*
|
|
113
|
-
* See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1218
|
|
114
|
-
*/
|
|
115
|
-
const foldSiblingsIntoBranches = (schema: any): any => {
|
|
116
|
-
const branchKey = schema?.oneOf
|
|
117
|
-
? "oneOf"
|
|
118
|
-
: schema?.anyOf
|
|
119
|
-
? "anyOf"
|
|
120
|
-
: undefined;
|
|
121
|
-
if (!branchKey) return schema;
|
|
122
|
-
|
|
123
|
-
const branches = schema[branchKey];
|
|
124
|
-
if (!Array.isArray(branches) || branches.length === 0) return schema;
|
|
125
|
-
|
|
126
|
-
const siblings = { ...schema };
|
|
127
|
-
delete siblings[branchKey];
|
|
128
|
-
if (Object.keys(siblings).length === 0) return schema;
|
|
129
|
-
|
|
130
|
-
const folded = branches.map((branch: any) =>
|
|
131
|
-
mergeAllOf({ allOf: [siblings, branch] })
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
return { [branchKey]: folded };
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Recursively searches for a property in a schema, including nested
|
|
139
|
-
* oneOf, anyOf, and allOf structures. This is needed for discriminators
|
|
140
|
-
* where the property definition may be in a nested schema.
|
|
141
|
-
*/
|
|
142
|
-
const findProperty = (
|
|
143
|
-
schema: SchemaObject,
|
|
144
|
-
propertyName: string
|
|
145
|
-
): SchemaObject | undefined => {
|
|
146
|
-
// Check direct properties first
|
|
147
|
-
if (schema.properties?.[propertyName]) {
|
|
148
|
-
return schema.properties[propertyName];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Search in oneOf schemas
|
|
152
|
-
if (schema.oneOf) {
|
|
153
|
-
for (const subschema of schema.oneOf) {
|
|
154
|
-
const found = findProperty(subschema as SchemaObject, propertyName);
|
|
155
|
-
if (found) return found;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Search in anyOf schemas
|
|
160
|
-
if (schema.anyOf) {
|
|
161
|
-
for (const subschema of schema.anyOf) {
|
|
162
|
-
const found = findProperty(subschema as SchemaObject, propertyName);
|
|
163
|
-
if (found) return found;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Search in allOf schemas
|
|
168
|
-
if (schema.allOf) {
|
|
169
|
-
for (const subschema of schema.allOf) {
|
|
170
|
-
const found = findProperty(subschema as SchemaObject, propertyName);
|
|
171
|
-
if (found) return found;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return undefined;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Recursively searches for a discriminator in a schema, including nested
|
|
180
|
-
* oneOf, anyOf, and allOf structures.
|
|
181
|
-
*/
|
|
182
|
-
const findDiscriminator = (schema: SchemaObject): any | undefined => {
|
|
183
|
-
if (schema.discriminator) {
|
|
184
|
-
return schema.discriminator;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (schema.oneOf) {
|
|
188
|
-
for (const subschema of schema.oneOf) {
|
|
189
|
-
const found = findDiscriminator(subschema as SchemaObject);
|
|
190
|
-
if (found) return found;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (schema.anyOf) {
|
|
195
|
-
for (const subschema of schema.anyOf) {
|
|
196
|
-
const found = findDiscriminator(subschema as SchemaObject);
|
|
197
|
-
if (found) return found;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (schema.allOf) {
|
|
202
|
-
for (const subschema of schema.allOf) {
|
|
203
|
-
const found = findDiscriminator(subschema as SchemaObject);
|
|
204
|
-
if (found) return found;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return undefined;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
39
|
interface MarkdownProps {
|
|
212
40
|
text: string | undefined;
|
|
213
41
|
}
|
|
@@ -388,6 +216,11 @@ const AnyOneOf: React.FC<SchemaProps> = ({
|
|
|
388
216
|
label={label}
|
|
389
217
|
value={`${uniqueId}-${index}-item`}
|
|
390
218
|
>
|
|
219
|
+
{anyOneSchema.description && (
|
|
220
|
+
<div style={{ marginLeft: "1rem" }}>
|
|
221
|
+
<MarkdownWrapper text={anyOneSchema.description} />
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
391
224
|
{/* Handle primitive types directly */}
|
|
392
225
|
{(isPrimitive(anyOneSchema) || anyOneSchema.const) && (
|
|
393
226
|
<SchemaItem
|
|
@@ -418,8 +251,16 @@ const AnyOneOf: React.FC<SchemaProps> = ({
|
|
|
418
251
|
|
|
419
252
|
{/* Handle actual object types with properties or nested schemas */}
|
|
420
253
|
{/* Note: In OpenAPI, properties implies type: object even if not explicitly set */}
|
|
254
|
+
{/* When the branch also has a nested oneOf/anyOf/allOf, skip
|
|
255
|
+
Properties here and delegate to SchemaNode below —
|
|
256
|
+
foldSiblingsIntoBranches will merge these properties into
|
|
257
|
+
each inner variant, so rendering them inline as well would
|
|
258
|
+
duplicate them per variant tab. See #1548. */}
|
|
421
259
|
{(anyOneSchema.type === "object" || !anyOneSchema.type) &&
|
|
422
|
-
anyOneSchema.properties &&
|
|
260
|
+
anyOneSchema.properties &&
|
|
261
|
+
!anyOneSchema.oneOf &&
|
|
262
|
+
!anyOneSchema.anyOf &&
|
|
263
|
+
!anyOneSchema.allOf && (
|
|
423
264
|
<Properties
|
|
424
265
|
schema={anyOneSchema}
|
|
425
266
|
schemaType={schemaType}
|
|
@@ -571,6 +412,13 @@ const PropertyDiscriminator: React.FC<SchemaEdgeProps> = ({
|
|
|
571
412
|
label={key}
|
|
572
413
|
value={`${index}-item-discriminator`}
|
|
573
414
|
>
|
|
415
|
+
{discriminator.mapping[key]?.description && (
|
|
416
|
+
<div style={{ marginLeft: "1rem" }}>
|
|
417
|
+
<MarkdownWrapper
|
|
418
|
+
text={discriminator.mapping[key].description}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
574
422
|
<SchemaNode
|
|
575
423
|
schema={discriminator.mapping[key]}
|
|
576
424
|
schemaType={schemaType}
|
|
@@ -616,9 +464,11 @@ const DiscriminatorNode: React.FC<DiscriminatorNodeProps> = ({
|
|
|
616
464
|
let discriminatedSchemas: any = {};
|
|
617
465
|
let inferredMapping: any = {};
|
|
618
466
|
|
|
619
|
-
// Search for the discriminator property in the schema, including nested
|
|
467
|
+
// Search for the discriminator property in the schema, including nested
|
|
468
|
+
// structures. Cached recursive lookup replaces the O(subtree) findProperty
|
|
469
|
+
// walk that caused #1525's O(N^2) render cost.
|
|
620
470
|
const discriminatorProperty =
|
|
621
|
-
|
|
471
|
+
findPropertyDeep(schema, discriminator.propertyName) ?? {};
|
|
622
472
|
|
|
623
473
|
if (schema.allOf) {
|
|
624
474
|
const mergedSchemas = mergeAllOf(schema) as SchemaObject;
|
|
@@ -699,6 +549,20 @@ const AdditionalProperties: React.FC<SchemaProps> = ({
|
|
|
699
549
|
|
|
700
550
|
if (!additionalProperties) return null;
|
|
701
551
|
|
|
552
|
+
if (isCircularMarker(additionalProperties)) {
|
|
553
|
+
return (
|
|
554
|
+
<SchemaItem
|
|
555
|
+
name="property name*"
|
|
556
|
+
required={false}
|
|
557
|
+
schemaName={additionalProperties}
|
|
558
|
+
schema={additionalProperties}
|
|
559
|
+
collapsible={false}
|
|
560
|
+
discriminator={false}
|
|
561
|
+
children={null}
|
|
562
|
+
/>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
702
566
|
// Handle free-form objects
|
|
703
567
|
if (additionalProperties === true || isEmpty(additionalProperties)) {
|
|
704
568
|
return (
|
|
@@ -812,6 +676,23 @@ const Items: React.FC<{
|
|
|
812
676
|
schemaType: "request" | "response";
|
|
813
677
|
schemaPath?: string;
|
|
814
678
|
}> = ({ schema, schemaType, schemaPath }) => {
|
|
679
|
+
if (isCircularMarker(schema.items)) {
|
|
680
|
+
return (
|
|
681
|
+
<div style={{ marginLeft: ".5rem" }}>
|
|
682
|
+
<OpeningArrayBracket />
|
|
683
|
+
<SchemaItem
|
|
684
|
+
collapsible={false}
|
|
685
|
+
name=""
|
|
686
|
+
schemaName={schema.items}
|
|
687
|
+
schema={schema.items}
|
|
688
|
+
discriminator={false}
|
|
689
|
+
children={null}
|
|
690
|
+
/>
|
|
691
|
+
<ClosingArrayBracket />
|
|
692
|
+
</div>
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
815
696
|
// Process schema.items to handle allOf merging
|
|
816
697
|
let itemsSchema = schema.items;
|
|
817
698
|
if (schema.items?.allOf) {
|
|
@@ -932,6 +813,20 @@ const SchemaEdge: React.FC<SchemaEdgeProps> = ({
|
|
|
932
813
|
return null;
|
|
933
814
|
}
|
|
934
815
|
|
|
816
|
+
if (isCircularMarker(schema)) {
|
|
817
|
+
return (
|
|
818
|
+
<SchemaItem
|
|
819
|
+
collapsible={false}
|
|
820
|
+
name={name}
|
|
821
|
+
required={Array.isArray(required) ? required.includes(name) : required}
|
|
822
|
+
schemaName={schema}
|
|
823
|
+
schema={schema}
|
|
824
|
+
discriminator={false}
|
|
825
|
+
children={null}
|
|
826
|
+
/>
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
935
830
|
const schemaName = getSchemaName(schema);
|
|
936
831
|
|
|
937
832
|
if (discriminator && discriminator.propertyName === name) {
|
|
@@ -1018,6 +913,20 @@ const SchemaEdge: React.FC<SchemaEdgeProps> = ({
|
|
|
1018
913
|
);
|
|
1019
914
|
}
|
|
1020
915
|
|
|
916
|
+
if (isCircularMarker(schema.items)) {
|
|
917
|
+
return (
|
|
918
|
+
<SchemaNodeDetails
|
|
919
|
+
name={name}
|
|
920
|
+
schemaName={schemaName}
|
|
921
|
+
required={required}
|
|
922
|
+
nullable={schema.nullable}
|
|
923
|
+
schema={schema}
|
|
924
|
+
schemaType={schemaType}
|
|
925
|
+
schemaPath={schemaPath}
|
|
926
|
+
/>
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
1021
930
|
if (schema.allOf) {
|
|
1022
931
|
// handle circular properties
|
|
1023
932
|
if (
|
|
@@ -1176,10 +1085,12 @@ function renderChildren(
|
|
|
1176
1085
|
}
|
|
1177
1086
|
|
|
1178
1087
|
const SchemaNode: React.FC<SchemaProps> = ({
|
|
1179
|
-
schema,
|
|
1088
|
+
schema: rawSchema,
|
|
1180
1089
|
schemaType,
|
|
1181
1090
|
schemaPath,
|
|
1182
1091
|
}) => {
|
|
1092
|
+
const schema = useMemo(() => normalizeSchema(rawSchema), [rawSchema]);
|
|
1093
|
+
|
|
1183
1094
|
if (
|
|
1184
1095
|
(schemaType === "request" && schema.readOnly) ||
|
|
1185
1096
|
(schemaType === "response" && schema.writeOnly)
|
|
@@ -1188,10 +1099,11 @@ const SchemaNode: React.FC<SchemaProps> = ({
|
|
|
1188
1099
|
}
|
|
1189
1100
|
|
|
1190
1101
|
// Resolve discriminator recursively so nested oneOf/anyOf/allOf compositions
|
|
1191
|
-
// can still render discriminator tabs.
|
|
1102
|
+
// can still render discriminator tabs. Cached via getDiscriminator so per-
|
|
1103
|
+
// render cost is O(1) amortized (see #1525).
|
|
1192
1104
|
let workingSchema = schema;
|
|
1193
1105
|
const resolvedDiscriminator =
|
|
1194
|
-
schema.discriminator ??
|
|
1106
|
+
schema.discriminator ?? getDiscriminator(schema);
|
|
1195
1107
|
if (schema.allOf && !schema.discriminator && resolvedDiscriminator) {
|
|
1196
1108
|
workingSchema = mergeAllOf(schema) as SchemaObject;
|
|
1197
1109
|
}
|