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
|
@@ -77,171 +77,14 @@ const DiscriminatorTabs_1 = __importDefault(
|
|
|
77
77
|
require("@theme/DiscriminatorTabs")
|
|
78
78
|
);
|
|
79
79
|
const Markdown_1 = __importDefault(require("@theme/Markdown"));
|
|
80
|
+
const normalize_1 = require("@theme/Schema/normalize");
|
|
80
81
|
const SchemaExpansion_1 = require("@theme/SchemaExpansion");
|
|
81
82
|
const SchemaItem_1 = __importDefault(require("@theme/SchemaItem"));
|
|
82
83
|
const SchemaTabs_1 = __importDefault(require("@theme/SchemaTabs"));
|
|
83
84
|
const TabItem_1 = __importDefault(require("@theme/TabItem"));
|
|
84
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
85
|
-
const allof_merge_1 = require("allof-merge");
|
|
86
85
|
const clsx_1 = __importDefault(require("clsx"));
|
|
87
86
|
const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
|
|
88
87
|
const schema_1 = require("../../markdown/schema");
|
|
89
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
90
|
-
// const jsonSchemaMergeAllOf = require("json-schema-merge-allof");
|
|
91
|
-
/**
|
|
92
|
-
* Strip `additionalProperties: false` from sibling allOf members so the
|
|
93
|
-
* strict-AND semantics of `allof-merge` don't collapse the result to an
|
|
94
|
-
* unsatisfiable empty schema.
|
|
95
|
-
*
|
|
96
|
-
* Per JSON Schema, two allOf members that each set `additionalProperties: false`
|
|
97
|
-
* with disjoint `properties` sets define an unsatisfiable schema (no value can
|
|
98
|
-
* satisfy both — each member rejects the other's properties). `allof-merge` is
|
|
99
|
-
* technically correct to drop all properties in that case, but it leaves the
|
|
100
|
-
* rendered schema blank.
|
|
101
|
-
*
|
|
102
|
-
* NSwag and Swashbuckle emit this pattern by default whenever a model uses
|
|
103
|
-
* inheritance/composition, so it's the dominant style for .NET-generated specs.
|
|
104
|
-
* Redoc, Swagger UI, and Stoplight all union the properties and ignore the
|
|
105
|
-
* conflicting flag — the approach this helper emulates by stripping the flag
|
|
106
|
-
* before delegating to `allof-merge`. The flag is render-irrelevant anyway:
|
|
107
|
-
* `additionalProperties: false` is treated identically to `undefined` by the
|
|
108
|
-
* AdditionalProperties component below (line ~641).
|
|
109
|
-
*
|
|
110
|
-
* Strips from every allOf member whenever the parent has ≥2 members. The
|
|
111
|
-
* collapse triggers symmetrically (both siblings strict) or asymmetrically
|
|
112
|
-
* (one strict member rejects another's properties as "additional"), so the
|
|
113
|
-
* presence of multiple members is the right gate. Single-member allOf is left
|
|
114
|
-
* alone — it can't conflict with anything.
|
|
115
|
-
*
|
|
116
|
-
* See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1119
|
|
117
|
-
* Mirrored in plugin: docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
|
|
118
|
-
*/
|
|
119
|
-
const stripConflictingAdditionalProps = (node) => {
|
|
120
|
-
if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
|
|
121
|
-
if (!node || typeof node !== "object") return node;
|
|
122
|
-
let working = node;
|
|
123
|
-
if (Array.isArray(node.allOf) && node.allOf.length > 1) {
|
|
124
|
-
const hasStrictMember = node.allOf.some(
|
|
125
|
-
(m) => m && m.additionalProperties === false
|
|
126
|
-
);
|
|
127
|
-
if (hasStrictMember) {
|
|
128
|
-
working = {
|
|
129
|
-
...node,
|
|
130
|
-
allOf: node.allOf.map((m) => {
|
|
131
|
-
if (m && m.additionalProperties === false) {
|
|
132
|
-
const { additionalProperties: _drop, ...rest } = m;
|
|
133
|
-
return rest;
|
|
134
|
-
}
|
|
135
|
-
return m;
|
|
136
|
-
}),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const result = {};
|
|
141
|
-
for (const [k, v] of Object.entries(working)) {
|
|
142
|
-
result[k] = stripConflictingAdditionalProps(v);
|
|
143
|
-
}
|
|
144
|
-
return result;
|
|
145
|
-
};
|
|
146
|
-
const mergeAllOf = (allOf) => {
|
|
147
|
-
const onMergeError = (msg) => {
|
|
148
|
-
console.warn(msg);
|
|
149
|
-
};
|
|
150
|
-
const mergedSchemas = (0, allof_merge_1.merge)(
|
|
151
|
-
stripConflictingAdditionalProps(allOf),
|
|
152
|
-
{
|
|
153
|
-
onMergeError,
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
return mergedSchemas ?? {};
|
|
157
|
-
};
|
|
158
|
-
/**
|
|
159
|
-
* Fold sibling fields into each `oneOf`/`anyOf` branch via allOf-merge, so each
|
|
160
|
-
* branch is self-contained. Mirrors Redoc's `SchemaModel.initOneOf` behavior.
|
|
161
|
-
* Without this, when an `allOf` override redefines a nested property with
|
|
162
|
-
* `oneOf`, the merged schema ends up with both `properties` and `oneOf` as
|
|
163
|
-
* siblings — and the renderer prints the shared properties twice.
|
|
164
|
-
*
|
|
165
|
-
* See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1218
|
|
166
|
-
*/
|
|
167
|
-
const foldSiblingsIntoBranches = (schema) => {
|
|
168
|
-
const branchKey = schema?.oneOf
|
|
169
|
-
? "oneOf"
|
|
170
|
-
: schema?.anyOf
|
|
171
|
-
? "anyOf"
|
|
172
|
-
: undefined;
|
|
173
|
-
if (!branchKey) return schema;
|
|
174
|
-
const branches = schema[branchKey];
|
|
175
|
-
if (!Array.isArray(branches) || branches.length === 0) return schema;
|
|
176
|
-
const siblings = { ...schema };
|
|
177
|
-
delete siblings[branchKey];
|
|
178
|
-
if (Object.keys(siblings).length === 0) return schema;
|
|
179
|
-
const folded = branches.map((branch) =>
|
|
180
|
-
mergeAllOf({ allOf: [siblings, branch] })
|
|
181
|
-
);
|
|
182
|
-
return { [branchKey]: folded };
|
|
183
|
-
};
|
|
184
|
-
/**
|
|
185
|
-
* Recursively searches for a property in a schema, including nested
|
|
186
|
-
* oneOf, anyOf, and allOf structures. This is needed for discriminators
|
|
187
|
-
* where the property definition may be in a nested schema.
|
|
188
|
-
*/
|
|
189
|
-
const findProperty = (schema, propertyName) => {
|
|
190
|
-
// Check direct properties first
|
|
191
|
-
if (schema.properties?.[propertyName]) {
|
|
192
|
-
return schema.properties[propertyName];
|
|
193
|
-
}
|
|
194
|
-
// Search in oneOf schemas
|
|
195
|
-
if (schema.oneOf) {
|
|
196
|
-
for (const subschema of schema.oneOf) {
|
|
197
|
-
const found = findProperty(subschema, propertyName);
|
|
198
|
-
if (found) return found;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
// Search in anyOf schemas
|
|
202
|
-
if (schema.anyOf) {
|
|
203
|
-
for (const subschema of schema.anyOf) {
|
|
204
|
-
const found = findProperty(subschema, propertyName);
|
|
205
|
-
if (found) return found;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// Search in allOf schemas
|
|
209
|
-
if (schema.allOf) {
|
|
210
|
-
for (const subschema of schema.allOf) {
|
|
211
|
-
const found = findProperty(subschema, propertyName);
|
|
212
|
-
if (found) return found;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return undefined;
|
|
216
|
-
};
|
|
217
|
-
/**
|
|
218
|
-
* Recursively searches for a discriminator in a schema, including nested
|
|
219
|
-
* oneOf, anyOf, and allOf structures.
|
|
220
|
-
*/
|
|
221
|
-
const findDiscriminator = (schema) => {
|
|
222
|
-
if (schema.discriminator) {
|
|
223
|
-
return schema.discriminator;
|
|
224
|
-
}
|
|
225
|
-
if (schema.oneOf) {
|
|
226
|
-
for (const subschema of schema.oneOf) {
|
|
227
|
-
const found = findDiscriminator(subschema);
|
|
228
|
-
if (found) return found;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (schema.anyOf) {
|
|
232
|
-
for (const subschema of schema.anyOf) {
|
|
233
|
-
const found = findDiscriminator(subschema);
|
|
234
|
-
if (found) return found;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (schema.allOf) {
|
|
238
|
-
for (const subschema of schema.allOf) {
|
|
239
|
-
const found = findDiscriminator(subschema);
|
|
240
|
-
if (found) return found;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return undefined;
|
|
244
|
-
};
|
|
245
88
|
// Renders string as markdown, useful for descriptions and qualifiers
|
|
246
89
|
const MarkdownWrapper = ({ text }) => {
|
|
247
90
|
return react_1.default.createElement(
|
|
@@ -392,6 +235,14 @@ const AnyOneOf = ({ schema, schemaType, schemaPath }) => {
|
|
|
392
235
|
react_1.default.createElement(
|
|
393
236
|
TabItem_1.default,
|
|
394
237
|
{ key: index, label: label, value: `${uniqueId}-${index}-item` },
|
|
238
|
+
anyOneSchema.description &&
|
|
239
|
+
react_1.default.createElement(
|
|
240
|
+
"div",
|
|
241
|
+
{ style: { marginLeft: "1rem" } },
|
|
242
|
+
react_1.default.createElement(MarkdownWrapper, {
|
|
243
|
+
text: anyOneSchema.description,
|
|
244
|
+
})
|
|
245
|
+
),
|
|
395
246
|
(isPrimitive(anyOneSchema) || anyOneSchema.const) &&
|
|
396
247
|
react_1.default.createElement(SchemaItem_1.default, {
|
|
397
248
|
collapsible: false,
|
|
@@ -416,6 +267,9 @@ const AnyOneOf = ({ schema, schemaType, schemaPath }) => {
|
|
|
416
267
|
}),
|
|
417
268
|
(anyOneSchema.type === "object" || !anyOneSchema.type) &&
|
|
418
269
|
anyOneSchema.properties &&
|
|
270
|
+
!anyOneSchema.oneOf &&
|
|
271
|
+
!anyOneSchema.anyOf &&
|
|
272
|
+
!anyOneSchema.allOf &&
|
|
419
273
|
react_1.default.createElement(Properties, {
|
|
420
274
|
schema: anyOneSchema,
|
|
421
275
|
schemaType: schemaType,
|
|
@@ -570,6 +424,14 @@ const PropertyDiscriminator = ({
|
|
|
570
424
|
react_1.default.createElement(
|
|
571
425
|
TabItem_1.default,
|
|
572
426
|
{ key: index, label: key, value: `${index}-item-discriminator` },
|
|
427
|
+
discriminator.mapping[key]?.description &&
|
|
428
|
+
react_1.default.createElement(
|
|
429
|
+
"div",
|
|
430
|
+
{ style: { marginLeft: "1rem" } },
|
|
431
|
+
react_1.default.createElement(MarkdownWrapper, {
|
|
432
|
+
text: discriminator.mapping[key].description,
|
|
433
|
+
})
|
|
434
|
+
),
|
|
573
435
|
react_1.default.createElement(SchemaNode, {
|
|
574
436
|
schema: discriminator.mapping[key],
|
|
575
437
|
schemaType: schemaType,
|
|
@@ -599,11 +461,13 @@ const PropertyDiscriminator = ({
|
|
|
599
461
|
const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
|
|
600
462
|
let discriminatedSchemas = {};
|
|
601
463
|
let inferredMapping = {};
|
|
602
|
-
// Search for the discriminator property in the schema, including nested
|
|
464
|
+
// Search for the discriminator property in the schema, including nested
|
|
465
|
+
// structures. Cached recursive lookup replaces the O(subtree) findProperty
|
|
466
|
+
// walk that caused #1525's O(N^2) render cost.
|
|
603
467
|
const discriminatorProperty =
|
|
604
|
-
|
|
468
|
+
(0, normalize_1.findPropertyDeep)(schema, discriminator.propertyName) ?? {};
|
|
605
469
|
if (schema.allOf) {
|
|
606
|
-
const mergedSchemas = mergeAllOf(schema);
|
|
470
|
+
const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
|
|
607
471
|
if (mergedSchemas.oneOf || mergedSchemas.anyOf) {
|
|
608
472
|
discriminatedSchemas = mergedSchemas.oneOf || mergedSchemas.anyOf;
|
|
609
473
|
}
|
|
@@ -623,7 +487,7 @@ const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
|
|
|
623
487
|
// Handle discriminated schema with allOf
|
|
624
488
|
let mergedSubSchema = {};
|
|
625
489
|
if (subSchema.allOf) {
|
|
626
|
-
mergedSubSchema = mergeAllOf(subSchema);
|
|
490
|
+
mergedSubSchema = (0, normalize_1.mergeAllOf)(subSchema);
|
|
627
491
|
}
|
|
628
492
|
const subProperties = subSchema.properties || mergedSubSchema.properties;
|
|
629
493
|
// Add a safeguard check to avoid referencing subProperties if it's undefined
|
|
@@ -664,6 +528,17 @@ const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
|
|
|
664
528
|
const AdditionalProperties = ({ schema, schemaType }) => {
|
|
665
529
|
const additionalProperties = schema.additionalProperties;
|
|
666
530
|
if (!additionalProperties) return null;
|
|
531
|
+
if ((0, normalize_1.isCircularMarker)(additionalProperties)) {
|
|
532
|
+
return react_1.default.createElement(SchemaItem_1.default, {
|
|
533
|
+
name: "property name*",
|
|
534
|
+
required: false,
|
|
535
|
+
schemaName: additionalProperties,
|
|
536
|
+
schema: additionalProperties,
|
|
537
|
+
collapsible: false,
|
|
538
|
+
discriminator: false,
|
|
539
|
+
children: null,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
667
542
|
// Handle free-form objects
|
|
668
543
|
if (
|
|
669
544
|
additionalProperties === true ||
|
|
@@ -774,10 +649,26 @@ const SchemaNodeDetails = ({
|
|
|
774
649
|
);
|
|
775
650
|
};
|
|
776
651
|
const Items = ({ schema, schemaType, schemaPath }) => {
|
|
652
|
+
if ((0, normalize_1.isCircularMarker)(schema.items)) {
|
|
653
|
+
return react_1.default.createElement(
|
|
654
|
+
"div",
|
|
655
|
+
{ style: { marginLeft: ".5rem" } },
|
|
656
|
+
react_1.default.createElement(ArrayBrackets_1.OpeningArrayBracket, null),
|
|
657
|
+
react_1.default.createElement(SchemaItem_1.default, {
|
|
658
|
+
collapsible: false,
|
|
659
|
+
name: "",
|
|
660
|
+
schemaName: schema.items,
|
|
661
|
+
schema: schema.items,
|
|
662
|
+
discriminator: false,
|
|
663
|
+
children: null,
|
|
664
|
+
}),
|
|
665
|
+
react_1.default.createElement(ArrayBrackets_1.ClosingArrayBracket, null)
|
|
666
|
+
);
|
|
667
|
+
}
|
|
777
668
|
// Process schema.items to handle allOf merging
|
|
778
669
|
let itemsSchema = schema.items;
|
|
779
670
|
if (schema.items?.allOf) {
|
|
780
|
-
itemsSchema = mergeAllOf(schema.items);
|
|
671
|
+
itemsSchema = (0, normalize_1.mergeAllOf)(schema.items);
|
|
781
672
|
}
|
|
782
673
|
// Handle complex schemas with multiple schema types
|
|
783
674
|
const hasOneOfAnyOf = itemsSchema?.oneOf || itemsSchema?.anyOf;
|
|
@@ -790,7 +681,7 @@ const Items = ({ schema, schemaType, schemaPath }) => {
|
|
|
790
681
|
// property rendering. See issue #1218.
|
|
791
682
|
const renderSchema =
|
|
792
683
|
hasOneOfAnyOf && hasProperties
|
|
793
|
-
? foldSiblingsIntoBranches(itemsSchema)
|
|
684
|
+
? (0, normalize_1.foldSiblingsIntoBranches)(itemsSchema)
|
|
794
685
|
: itemsSchema;
|
|
795
686
|
const renderHasProperties =
|
|
796
687
|
hasOneOfAnyOf && hasProperties ? false : hasProperties;
|
|
@@ -874,6 +765,17 @@ const SchemaEdge = ({
|
|
|
874
765
|
) {
|
|
875
766
|
return null;
|
|
876
767
|
}
|
|
768
|
+
if ((0, normalize_1.isCircularMarker)(schema)) {
|
|
769
|
+
return react_1.default.createElement(SchemaItem_1.default, {
|
|
770
|
+
collapsible: false,
|
|
771
|
+
name: name,
|
|
772
|
+
required: Array.isArray(required) ? required.includes(name) : required,
|
|
773
|
+
schemaName: schema,
|
|
774
|
+
schema: schema,
|
|
775
|
+
discriminator: false,
|
|
776
|
+
children: null,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
877
779
|
const schemaName = (0, schema_1.getSchemaName)(schema);
|
|
878
780
|
if (discriminator && discriminator.propertyName === name) {
|
|
879
781
|
return react_1.default.createElement(PropertyDiscriminator, {
|
|
@@ -941,6 +843,17 @@ const SchemaEdge = ({
|
|
|
941
843
|
schemaPath: schemaPath,
|
|
942
844
|
});
|
|
943
845
|
}
|
|
846
|
+
if ((0, normalize_1.isCircularMarker)(schema.items)) {
|
|
847
|
+
return react_1.default.createElement(SchemaNodeDetails, {
|
|
848
|
+
name: name,
|
|
849
|
+
schemaName: schemaName,
|
|
850
|
+
required: required,
|
|
851
|
+
nullable: schema.nullable,
|
|
852
|
+
schema: schema,
|
|
853
|
+
schemaType: schemaType,
|
|
854
|
+
schemaPath: schemaPath,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
944
857
|
if (schema.allOf) {
|
|
945
858
|
// handle circular properties
|
|
946
859
|
if (
|
|
@@ -959,7 +872,7 @@ const SchemaEdge = ({
|
|
|
959
872
|
children: null,
|
|
960
873
|
});
|
|
961
874
|
}
|
|
962
|
-
const mergedSchemas = mergeAllOf(schema);
|
|
875
|
+
const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
|
|
963
876
|
if (
|
|
964
877
|
(schemaType === "request" && mergedSchemas.readOnly) ||
|
|
965
878
|
(schemaType === "response" && mergedSchemas.writeOnly)
|
|
@@ -1026,7 +939,9 @@ function renderChildren(schema, schemaType, schemaPath) {
|
|
|
1026
939
|
const hasOneOfAnyOf = !!(schema.oneOf || schema.anyOf);
|
|
1027
940
|
const hasProperties = !!schema.properties;
|
|
1028
941
|
const folded =
|
|
1029
|
-
hasOneOfAnyOf && hasProperties
|
|
942
|
+
hasOneOfAnyOf && hasProperties
|
|
943
|
+
? (0, normalize_1.foldSiblingsIntoBranches)(schema)
|
|
944
|
+
: schema;
|
|
1030
945
|
const renderProperties =
|
|
1031
946
|
hasOneOfAnyOf && hasProperties ? false : hasProperties;
|
|
1032
947
|
return react_1.default.createElement(
|
|
@@ -1063,7 +978,11 @@ function renderChildren(schema, schemaType, schemaPath) {
|
|
|
1063
978
|
})
|
|
1064
979
|
);
|
|
1065
980
|
}
|
|
1066
|
-
const SchemaNode = ({ schema, schemaType, schemaPath }) => {
|
|
981
|
+
const SchemaNode = ({ schema: rawSchema, schemaType, schemaPath }) => {
|
|
982
|
+
const schema = (0, react_1.useMemo)(
|
|
983
|
+
() => (0, normalize_1.normalizeSchema)(rawSchema),
|
|
984
|
+
[rawSchema]
|
|
985
|
+
);
|
|
1067
986
|
if (
|
|
1068
987
|
(schemaType === "request" && schema.readOnly) ||
|
|
1069
988
|
(schemaType === "response" && schema.writeOnly)
|
|
@@ -1071,12 +990,13 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
|
|
|
1071
990
|
return null;
|
|
1072
991
|
}
|
|
1073
992
|
// Resolve discriminator recursively so nested oneOf/anyOf/allOf compositions
|
|
1074
|
-
// can still render discriminator tabs.
|
|
993
|
+
// can still render discriminator tabs. Cached via getDiscriminator so per-
|
|
994
|
+
// render cost is O(1) amortized (see #1525).
|
|
1075
995
|
let workingSchema = schema;
|
|
1076
996
|
const resolvedDiscriminator =
|
|
1077
|
-
schema.discriminator ??
|
|
997
|
+
schema.discriminator ?? (0, normalize_1.getDiscriminator)(schema);
|
|
1078
998
|
if (schema.allOf && !schema.discriminator && resolvedDiscriminator) {
|
|
1079
|
-
workingSchema = mergeAllOf(schema);
|
|
999
|
+
workingSchema = (0, normalize_1.mergeAllOf)(schema);
|
|
1080
1000
|
}
|
|
1081
1001
|
if (!workingSchema.discriminator && resolvedDiscriminator) {
|
|
1082
1002
|
workingSchema.discriminator = resolvedDiscriminator;
|
|
@@ -1096,7 +1016,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
|
|
|
1096
1016
|
const hasMultipleChoices = oneOfItems.length > 1;
|
|
1097
1017
|
if (hasMultipleChoices) {
|
|
1098
1018
|
// Render each oneOf/anyOf constraint first, then shared properties
|
|
1099
|
-
const mergedSchemas = mergeAllOf(schema);
|
|
1019
|
+
const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
|
|
1100
1020
|
if (
|
|
1101
1021
|
(schemaType === "request" && mergedSchemas.readOnly) ||
|
|
1102
1022
|
(schemaType === "response" && mergedSchemas.writeOnly)
|
|
@@ -1138,7 +1058,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
|
|
|
1138
1058
|
);
|
|
1139
1059
|
}
|
|
1140
1060
|
// For other allOf cases, use standard merge behavior
|
|
1141
|
-
const mergedSchemas = mergeAllOf(schema);
|
|
1061
|
+
const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
|
|
1142
1062
|
if (
|
|
1143
1063
|
(schemaType === "request" && mergedSchemas.readOnly) ||
|
|
1144
1064
|
(schemaType === "response" && mergedSchemas.writeOnly)
|
|
@@ -1151,7 +1071,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
|
|
|
1151
1071
|
const hasProperties = !!mergedSchemas.properties;
|
|
1152
1072
|
const folded =
|
|
1153
1073
|
hasOneOfAnyOf && hasProperties
|
|
1154
|
-
? foldSiblingsIntoBranches(mergedSchemas)
|
|
1074
|
+
? (0, normalize_1.foldSiblingsIntoBranches)(mergedSchemas)
|
|
1155
1075
|
: mergedSchemas;
|
|
1156
1076
|
const renderProperties =
|
|
1157
1077
|
hasOneOfAnyOf && hasProperties ? false : hasProperties;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare function isCircularMarker(value: unknown): value is string;
|
|
2
|
+
export declare function mergeAllOf(schema: any): any;
|
|
3
|
+
/**
|
|
4
|
+
* Fold sibling fields into each oneOf/anyOf branch via allOf-merge so each
|
|
5
|
+
* branch is self-contained. See #1218.
|
|
6
|
+
*
|
|
7
|
+
* Skipped when a discriminator is present: DiscriminatorNode already renders
|
|
8
|
+
* sibling properties at the top level, and folding would duplicate the
|
|
9
|
+
* discriminator metadata into each branch (causing nested DiscriminatorNode
|
|
10
|
+
* renders inside tabs — see #1525 follow-up).
|
|
11
|
+
*
|
|
12
|
+
* Called by normalizeSchema after allOf resolution. Uses mergeAllOf internally
|
|
13
|
+
* to compose `{ allOf: [siblings, branch] }` per branch — the WeakMap caches
|
|
14
|
+
* in stripConflictingAdditionalProps prevent redundant work on shared subtrees.
|
|
15
|
+
*/
|
|
16
|
+
export declare function foldSiblingsIntoBranches(schema: any): any;
|
|
17
|
+
export declare function getDiscriminator(schema: any): any | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Cached recursive property lookup. Searches schema.properties first, then
|
|
20
|
+
* into oneOf/anyOf/allOf branches. Returns O(1) on repeated calls with the
|
|
21
|
+
* same (schema, propertyName) pair — replaces the O(subtree) findProperty
|
|
22
|
+
* walk that caused #1525's O(N^2) render cost.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findPropertyDeep(schema: any, propertyName: string): any | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Identity-stable schema pass-through. Returns the input as-is so SchemaNode
|
|
27
|
+
* useMemo dependencies stay stable across renders.
|
|
28
|
+
*
|
|
29
|
+
* Earlier iterations of this function eagerly merged allOf and folded
|
|
30
|
+
* siblings into oneOf/anyOf branches, but those operations need to stay
|
|
31
|
+
* conditional inside SchemaNode and DiscriminatorNode — eager versions
|
|
32
|
+
* caused cartesian product expansion (allOf with multiple oneOfs) and
|
|
33
|
+
* double-folding regressions. The render-time helpers below
|
|
34
|
+
* (mergeAllOf, foldSiblingsIntoBranches) are still cached via the WeakMap
|
|
35
|
+
* inside stripConflictingAdditionalProps, so per-render cost stays bounded.
|
|
36
|
+
*
|
|
37
|
+
* The perf win for #1525 comes from getDiscriminator's WeakMap cache
|
|
38
|
+
* replacing the O(subtree) findDiscriminator walk, not from eager
|
|
39
|
+
* normalization here.
|
|
40
|
+
*/
|
|
41
|
+
export declare function normalizeSchema(schema: any): any;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* ============================================================================
|
|
3
|
+
* Copyright (c) Palo Alto Networks
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
* ========================================================================== */
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.isCircularMarker = isCircularMarker;
|
|
10
|
+
exports.mergeAllOf = mergeAllOf;
|
|
11
|
+
exports.foldSiblingsIntoBranches = foldSiblingsIntoBranches;
|
|
12
|
+
exports.getDiscriminator = getDiscriminator;
|
|
13
|
+
exports.findPropertyDeep = findPropertyDeep;
|
|
14
|
+
exports.normalizeSchema = normalizeSchema;
|
|
15
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
16
|
+
const allof_merge_1 = require("allof-merge");
|
|
17
|
+
function isCircularMarker(value) {
|
|
18
|
+
return typeof value === "string" && value.startsWith("circular(");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Strip `additionalProperties: false` from sibling allOf members so
|
|
22
|
+
* allof-merge doesn't collapse to an unsatisfiable empty schema. See #1119.
|
|
23
|
+
*
|
|
24
|
+
* NOT cached: DiscriminatorNode mutates raw subschemas (deletes the
|
|
25
|
+
* discriminator property from each branch to avoid duplicate rendering).
|
|
26
|
+
* A WeakMap cache would return the pre-mutation deep clone on subsequent
|
|
27
|
+
* mergeAllOf calls, causing deleted properties to reappear inside tab
|
|
28
|
+
* content. Matches prod's per-call behavior.
|
|
29
|
+
*/
|
|
30
|
+
function stripConflictingAdditionalProps(node) {
|
|
31
|
+
if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
|
|
32
|
+
if (!node || typeof node !== "object") return node;
|
|
33
|
+
let working = node;
|
|
34
|
+
if (Array.isArray(node.allOf) && node.allOf.length > 1) {
|
|
35
|
+
const hasStrictMember = node.allOf.some(
|
|
36
|
+
(m) => m && m.additionalProperties === false
|
|
37
|
+
);
|
|
38
|
+
if (hasStrictMember) {
|
|
39
|
+
working = {
|
|
40
|
+
...node,
|
|
41
|
+
allOf: node.allOf.map((m) => {
|
|
42
|
+
if (m && m.additionalProperties === false) {
|
|
43
|
+
const { additionalProperties: _drop, ...rest } = m;
|
|
44
|
+
return rest;
|
|
45
|
+
}
|
|
46
|
+
return m;
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const [k, v] of Object.entries(working)) {
|
|
53
|
+
result[k] = stripConflictingAdditionalProps(v);
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function mergeAllOf(schema) {
|
|
58
|
+
const onMergeError = (msg) => console.warn(msg);
|
|
59
|
+
const merged = (0, allof_merge_1.merge)(
|
|
60
|
+
stripConflictingAdditionalProps(schema),
|
|
61
|
+
{
|
|
62
|
+
onMergeError,
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
return merged ?? {};
|
|
66
|
+
}
|
|
67
|
+
// Keys that are parent-level metadata and should NOT be folded into branches.
|
|
68
|
+
const METADATA_KEYS = new Set([
|
|
69
|
+
"title",
|
|
70
|
+
"description",
|
|
71
|
+
"discriminator",
|
|
72
|
+
"deprecated",
|
|
73
|
+
"externalDocs",
|
|
74
|
+
"example",
|
|
75
|
+
"examples",
|
|
76
|
+
"xml",
|
|
77
|
+
"nullable",
|
|
78
|
+
"readOnly",
|
|
79
|
+
"writeOnly",
|
|
80
|
+
"default",
|
|
81
|
+
]);
|
|
82
|
+
/**
|
|
83
|
+
* Fold sibling fields into each oneOf/anyOf branch via allOf-merge so each
|
|
84
|
+
* branch is self-contained. See #1218.
|
|
85
|
+
*
|
|
86
|
+
* Skipped when a discriminator is present: DiscriminatorNode already renders
|
|
87
|
+
* sibling properties at the top level, and folding would duplicate the
|
|
88
|
+
* discriminator metadata into each branch (causing nested DiscriminatorNode
|
|
89
|
+
* renders inside tabs — see #1525 follow-up).
|
|
90
|
+
*
|
|
91
|
+
* Called by normalizeSchema after allOf resolution. Uses mergeAllOf internally
|
|
92
|
+
* to compose `{ allOf: [siblings, branch] }` per branch — the WeakMap caches
|
|
93
|
+
* in stripConflictingAdditionalProps prevent redundant work on shared subtrees.
|
|
94
|
+
*/
|
|
95
|
+
function foldSiblingsIntoBranches(schema) {
|
|
96
|
+
const branchKey = schema?.oneOf
|
|
97
|
+
? "oneOf"
|
|
98
|
+
: schema?.anyOf
|
|
99
|
+
? "anyOf"
|
|
100
|
+
: undefined;
|
|
101
|
+
if (!branchKey) return schema;
|
|
102
|
+
const branches = schema[branchKey];
|
|
103
|
+
if (!Array.isArray(branches) || branches.length === 0) return schema;
|
|
104
|
+
// Discriminator schemas rely on top-level properties being intact so
|
|
105
|
+
// DiscriminatorNode can locate the discriminator property and render
|
|
106
|
+
// shared siblings at the top level. Leave them alone.
|
|
107
|
+
if (schema.discriminator) return schema;
|
|
108
|
+
// Build siblings from non-metadata keys only. Metadata (title, description,
|
|
109
|
+
// examples, etc.) belongs at the top level and must not be folded.
|
|
110
|
+
const siblings = {};
|
|
111
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
112
|
+
if (key !== branchKey && !METADATA_KEYS.has(key) && !key.startsWith("x-")) {
|
|
113
|
+
siblings[key] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(siblings).length === 0) return schema;
|
|
117
|
+
const folded = branches.map((branch) =>
|
|
118
|
+
mergeAllOf({ allOf: [siblings, branch] })
|
|
119
|
+
);
|
|
120
|
+
const result = { [branchKey]: folded };
|
|
121
|
+
for (const key of Object.keys(schema)) {
|
|
122
|
+
if (key !== branchKey && (METADATA_KEYS.has(key) || key.startsWith("x-"))) {
|
|
123
|
+
result[key] = schema[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
// WeakMap caches keyed by object identity — shared-reference subtrees from
|
|
129
|
+
// $RefParser.dereference are looked up once, and cycles short-circuit.
|
|
130
|
+
const discriminatorCache = new WeakMap();
|
|
131
|
+
const findPropertyCache = new WeakMap();
|
|
132
|
+
/**
|
|
133
|
+
* Cached recursive discriminator lookup. Returns O(1) on repeated calls
|
|
134
|
+
* with the same object reference (common after normalizeSchema).
|
|
135
|
+
*/
|
|
136
|
+
const BRANCH_KEYS = ["oneOf", "anyOf", "allOf"];
|
|
137
|
+
function getDiscriminator(schema) {
|
|
138
|
+
if (!schema || typeof schema !== "object") return undefined;
|
|
139
|
+
if (discriminatorCache.has(schema)) {
|
|
140
|
+
const cached = discriminatorCache.get(schema);
|
|
141
|
+
return cached === null ? undefined : cached;
|
|
142
|
+
}
|
|
143
|
+
discriminatorCache.set(schema, null);
|
|
144
|
+
let result = schema.discriminator;
|
|
145
|
+
if (!result) {
|
|
146
|
+
for (const key of BRANCH_KEYS) {
|
|
147
|
+
const branches = schema[key];
|
|
148
|
+
if (!Array.isArray(branches)) continue;
|
|
149
|
+
for (const branch of branches) {
|
|
150
|
+
result = getDiscriminator(branch);
|
|
151
|
+
if (result) break;
|
|
152
|
+
}
|
|
153
|
+
if (result) break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
discriminatorCache.set(schema, result ?? null);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cached recursive property lookup. Searches schema.properties first, then
|
|
161
|
+
* into oneOf/anyOf/allOf branches. Returns O(1) on repeated calls with the
|
|
162
|
+
* same (schema, propertyName) pair — replaces the O(subtree) findProperty
|
|
163
|
+
* walk that caused #1525's O(N^2) render cost.
|
|
164
|
+
*/
|
|
165
|
+
function findPropertyDeep(schema, propertyName) {
|
|
166
|
+
if (!schema || typeof schema !== "object") return undefined;
|
|
167
|
+
let cache = findPropertyCache.get(schema);
|
|
168
|
+
if (cache && cache.has(propertyName)) {
|
|
169
|
+
const cached = cache.get(propertyName);
|
|
170
|
+
return cached === null ? undefined : cached;
|
|
171
|
+
}
|
|
172
|
+
if (!cache) {
|
|
173
|
+
cache = new Map();
|
|
174
|
+
findPropertyCache.set(schema, cache);
|
|
175
|
+
}
|
|
176
|
+
cache.set(propertyName, null);
|
|
177
|
+
let result = schema.properties?.[propertyName];
|
|
178
|
+
if (!result) {
|
|
179
|
+
for (const key of BRANCH_KEYS) {
|
|
180
|
+
const branches = schema[key];
|
|
181
|
+
if (!Array.isArray(branches)) continue;
|
|
182
|
+
for (const branch of branches) {
|
|
183
|
+
result = findPropertyDeep(branch, propertyName);
|
|
184
|
+
if (result) break;
|
|
185
|
+
}
|
|
186
|
+
if (result) break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
cache.set(propertyName, result ?? null);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Identity-stable schema pass-through. Returns the input as-is so SchemaNode
|
|
194
|
+
* useMemo dependencies stay stable across renders.
|
|
195
|
+
*
|
|
196
|
+
* Earlier iterations of this function eagerly merged allOf and folded
|
|
197
|
+
* siblings into oneOf/anyOf branches, but those operations need to stay
|
|
198
|
+
* conditional inside SchemaNode and DiscriminatorNode — eager versions
|
|
199
|
+
* caused cartesian product expansion (allOf with multiple oneOfs) and
|
|
200
|
+
* double-folding regressions. The render-time helpers below
|
|
201
|
+
* (mergeAllOf, foldSiblingsIntoBranches) are still cached via the WeakMap
|
|
202
|
+
* inside stripConflictingAdditionalProps, so per-render cost stays bounded.
|
|
203
|
+
*
|
|
204
|
+
* The perf win for #1525 comes from getDiscriminator's WeakMap cache
|
|
205
|
+
* replacing the O(subtree) findDiscriminator walk, not from eager
|
|
206
|
+
* normalization here.
|
|
207
|
+
*/
|
|
208
|
+
function normalizeSchema(schema) {
|
|
209
|
+
return schema;
|
|
210
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|