docusaurus-theme-openapi-docs 0.0.0-1313 → 0.0.0-1330
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 +74 -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 +271 -0
- package/package.json +3 -3
- package/src/theme/Schema/index.tsx +78 -186
- package/src/theme/Schema/normalize.test.ts +288 -0
- package/src/theme/Schema/normalize.ts +224 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
findPropertyDeep,
|
|
10
|
+
foldSiblingsIntoBranches,
|
|
11
|
+
getDiscriminator,
|
|
12
|
+
isCircularMarker,
|
|
13
|
+
mergeAllOf,
|
|
14
|
+
normalizeSchema,
|
|
15
|
+
} from "./normalize";
|
|
16
|
+
|
|
17
|
+
describe("normalizeSchema", () => {
|
|
18
|
+
it("returns the input as-is (identity-stable pass-through)", () => {
|
|
19
|
+
const schema = { type: "object", properties: { a: { type: "string" } } };
|
|
20
|
+
expect(normalizeSchema(schema)).toBe(schema);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles null, undefined, and primitives", () => {
|
|
24
|
+
expect(normalizeSchema(null)).toBeNull();
|
|
25
|
+
expect(normalizeSchema(undefined)).toBeUndefined();
|
|
26
|
+
expect(normalizeSchema("string")).toBe("string");
|
|
27
|
+
expect(normalizeSchema(42)).toBe(42);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("mergeAllOf", () => {
|
|
32
|
+
it("merges allOf members into a flat schema", () => {
|
|
33
|
+
const schema = {
|
|
34
|
+
allOf: [
|
|
35
|
+
{ type: "object", properties: { a: { type: "string" } } },
|
|
36
|
+
{ type: "object", properties: { b: { type: "number" } } },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
const result = mergeAllOf(schema);
|
|
40
|
+
expect(result.allOf).toBeUndefined();
|
|
41
|
+
expect(result.properties.a).toEqual({ type: "string" });
|
|
42
|
+
expect(result.properties.b).toEqual({ type: "number" });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("strips conflicting additionalProperties: false from allOf members", () => {
|
|
46
|
+
const schema = {
|
|
47
|
+
allOf: [
|
|
48
|
+
{
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: { a: { type: "string" } },
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: { b: { type: "number" } },
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
const result = mergeAllOf(schema);
|
|
61
|
+
expect(result.properties.a).toBeDefined();
|
|
62
|
+
expect(result.properties.b).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("preserves both properties and oneOf when merging incompatible-shape members", () => {
|
|
66
|
+
const result = mergeAllOf({
|
|
67
|
+
allOf: [
|
|
68
|
+
{ type: "object", properties: { id: { type: "string" } } },
|
|
69
|
+
{ oneOf: [{ title: "a" }, { title: "b" }] },
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
expect(result.properties.id).toBeDefined();
|
|
73
|
+
expect(result.oneOf).toHaveLength(2);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("foldSiblingsIntoBranches", () => {
|
|
78
|
+
it("returns schema unchanged when no oneOf/anyOf is present", () => {
|
|
79
|
+
const schema = { type: "object", properties: { a: { type: "string" } } };
|
|
80
|
+
expect(foldSiblingsIntoBranches(schema)).toBe(schema);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns schema unchanged when branches have no siblings", () => {
|
|
84
|
+
const schema = {
|
|
85
|
+
oneOf: [{ properties: { a: { type: "string" } } }],
|
|
86
|
+
};
|
|
87
|
+
expect(foldSiblingsIntoBranches(schema)).toBe(schema);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns schema unchanged when a discriminator is present", () => {
|
|
91
|
+
const schema = {
|
|
92
|
+
discriminator: { propertyName: "type" },
|
|
93
|
+
properties: { shared: { type: "string" } },
|
|
94
|
+
oneOf: [{ properties: { a: { type: "number" } } }],
|
|
95
|
+
};
|
|
96
|
+
expect(foldSiblingsIntoBranches(schema)).toBe(schema);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("folds sibling properties into oneOf branches", () => {
|
|
100
|
+
const schema = {
|
|
101
|
+
properties: { shared: { type: "string" } },
|
|
102
|
+
oneOf: [
|
|
103
|
+
{ properties: { a: { type: "number" } } },
|
|
104
|
+
{ properties: { b: { type: "boolean" } } },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
const result = foldSiblingsIntoBranches(schema);
|
|
108
|
+
expect(result.properties).toBeUndefined();
|
|
109
|
+
expect(result.oneOf).toHaveLength(2);
|
|
110
|
+
expect(result.oneOf[0].properties.shared).toBeDefined();
|
|
111
|
+
expect(result.oneOf[0].properties.a).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("folds sibling properties into anyOf branches", () => {
|
|
115
|
+
const schema = {
|
|
116
|
+
properties: { shared: { type: "string" } },
|
|
117
|
+
anyOf: [
|
|
118
|
+
{ properties: { a: { type: "number" } } },
|
|
119
|
+
{ properties: { b: { type: "boolean" } } },
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
const result = foldSiblingsIntoBranches(schema);
|
|
123
|
+
expect(result.properties).toBeUndefined();
|
|
124
|
+
expect(result.anyOf).toHaveLength(2);
|
|
125
|
+
expect(result.anyOf[1].properties.shared).toBeDefined();
|
|
126
|
+
expect(result.anyOf[1].properties.b).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("preserves metadata and x-extension keys when folding", () => {
|
|
130
|
+
const schema = {
|
|
131
|
+
title: "Test",
|
|
132
|
+
description: "desc",
|
|
133
|
+
deprecated: true,
|
|
134
|
+
"x-vendor": true,
|
|
135
|
+
properties: { shared: { type: "string" } },
|
|
136
|
+
anyOf: [{ properties: { a: { type: "number" } } }],
|
|
137
|
+
};
|
|
138
|
+
const result = foldSiblingsIntoBranches(schema);
|
|
139
|
+
expect(result.title).toBe("Test");
|
|
140
|
+
expect(result.description).toBe("desc");
|
|
141
|
+
expect(result.deprecated).toBe(true);
|
|
142
|
+
expect(result["x-vendor"]).toBe(true);
|
|
143
|
+
expect(result.properties).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("strips non-metadata keys from result", () => {
|
|
147
|
+
const schema = {
|
|
148
|
+
type: "object",
|
|
149
|
+
required: ["shared"],
|
|
150
|
+
properties: { shared: { type: "string" } },
|
|
151
|
+
items: { type: "string" },
|
|
152
|
+
oneOf: [{ properties: { a: { type: "number" } } }],
|
|
153
|
+
};
|
|
154
|
+
const result = foldSiblingsIntoBranches(schema);
|
|
155
|
+
expect(result.type).toBeUndefined();
|
|
156
|
+
expect(result.required).toBeUndefined();
|
|
157
|
+
expect(result.properties).toBeUndefined();
|
|
158
|
+
expect(result.items).toBeUndefined();
|
|
159
|
+
expect(result.oneOf).toHaveLength(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("getDiscriminator", () => {
|
|
164
|
+
it("returns direct discriminator", () => {
|
|
165
|
+
const schema = { discriminator: { propertyName: "type" } };
|
|
166
|
+
expect(getDiscriminator(schema)).toEqual({ propertyName: "type" });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("finds discriminator in oneOf branches", () => {
|
|
170
|
+
const schema = {
|
|
171
|
+
oneOf: [{ discriminator: { propertyName: "kind" } }, { type: "string" }],
|
|
172
|
+
};
|
|
173
|
+
expect(getDiscriminator(schema)).toEqual({ propertyName: "kind" });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("finds discriminator in anyOf branches", () => {
|
|
177
|
+
const schema = {
|
|
178
|
+
anyOf: [
|
|
179
|
+
{ type: "string" },
|
|
180
|
+
{ discriminator: { propertyName: "variant" } },
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
expect(getDiscriminator(schema)).toEqual({ propertyName: "variant" });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("finds discriminator in allOf branches", () => {
|
|
187
|
+
const schema = {
|
|
188
|
+
allOf: [
|
|
189
|
+
{ properties: { id: { type: "string" } } },
|
|
190
|
+
{ discriminator: { propertyName: "type" } },
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
expect(getDiscriminator(schema)).toEqual({ propertyName: "type" });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("returns undefined when no discriminator exists", () => {
|
|
197
|
+
const schema = {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: { name: { type: "string" } },
|
|
200
|
+
};
|
|
201
|
+
expect(getDiscriminator(schema)).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("returns undefined for null/primitive input", () => {
|
|
205
|
+
expect(getDiscriminator(null)).toBeUndefined();
|
|
206
|
+
expect(getDiscriminator("string")).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("caches results for same object reference", () => {
|
|
210
|
+
const inner = { discriminator: { propertyName: "type" } };
|
|
211
|
+
const schema = { oneOf: [inner] };
|
|
212
|
+
const first = getDiscriminator(schema);
|
|
213
|
+
const second = getDiscriminator(schema);
|
|
214
|
+
expect(first).toBe(second);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("handles cycles without infinite recursion", () => {
|
|
218
|
+
const a: any = { properties: {} };
|
|
219
|
+
const b: any = { properties: { a } };
|
|
220
|
+
a.properties.b = b;
|
|
221
|
+
expect(() => getDiscriminator(a)).not.toThrow();
|
|
222
|
+
expect(getDiscriminator(a)).toBeUndefined();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("findPropertyDeep", () => {
|
|
227
|
+
it("returns property at top level", () => {
|
|
228
|
+
const schema = { properties: { foo: { type: "string" } } };
|
|
229
|
+
expect(findPropertyDeep(schema, "foo")).toEqual({ type: "string" });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("finds property in oneOf branches", () => {
|
|
233
|
+
const schema = {
|
|
234
|
+
oneOf: [
|
|
235
|
+
{ properties: { a: { type: "string" } } },
|
|
236
|
+
{ properties: { b: { type: "number" } } },
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
expect(findPropertyDeep(schema, "a")).toEqual({ type: "string" });
|
|
240
|
+
expect(findPropertyDeep(schema, "b")).toEqual({ type: "number" });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("finds property in anyOf branches", () => {
|
|
244
|
+
const schema = {
|
|
245
|
+
anyOf: [{ properties: { x: { type: "integer" } } }],
|
|
246
|
+
};
|
|
247
|
+
expect(findPropertyDeep(schema, "x")).toEqual({ type: "integer" });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("finds property in allOf branches", () => {
|
|
251
|
+
const schema = {
|
|
252
|
+
allOf: [{ properties: { y: { type: "boolean" } } }],
|
|
253
|
+
};
|
|
254
|
+
expect(findPropertyDeep(schema, "y")).toEqual({ type: "boolean" });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("returns undefined when property not found", () => {
|
|
258
|
+
const schema = { properties: { foo: { type: "string" } } };
|
|
259
|
+
expect(findPropertyDeep(schema, "bar")).toBeUndefined();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("returns undefined for null/primitive input", () => {
|
|
263
|
+
expect(findPropertyDeep(null, "foo")).toBeUndefined();
|
|
264
|
+
expect(findPropertyDeep("string", "foo")).toBeUndefined();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("handles cycles without infinite recursion", () => {
|
|
268
|
+
const a: any = { properties: {} };
|
|
269
|
+
a.oneOf = [a];
|
|
270
|
+
expect(() => findPropertyDeep(a, "foo")).not.toThrow();
|
|
271
|
+
expect(findPropertyDeep(a, "foo")).toBeUndefined();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("isCircularMarker", () => {
|
|
276
|
+
it("returns true for circular marker strings", () => {
|
|
277
|
+
expect(isCircularMarker("circular(Title)")).toBe(true);
|
|
278
|
+
expect(isCircularMarker("circular()")).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("returns false for other values", () => {
|
|
282
|
+
expect(isCircularMarker("normal string")).toBe(false);
|
|
283
|
+
expect(isCircularMarker(null)).toBe(false);
|
|
284
|
+
expect(isCircularMarker(undefined)).toBe(false);
|
|
285
|
+
expect(isCircularMarker({})).toBe(false);
|
|
286
|
+
expect(isCircularMarker(42)).toBe(false);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
9
|
+
import { merge } from "allof-merge";
|
|
10
|
+
|
|
11
|
+
export function isCircularMarker(value: unknown): value is string {
|
|
12
|
+
return typeof value === "string" && value.startsWith("circular(");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Strip `additionalProperties: false` from sibling allOf members so
|
|
17
|
+
* allof-merge doesn't collapse to an unsatisfiable empty schema. See #1119.
|
|
18
|
+
*
|
|
19
|
+
* NOT cached: DiscriminatorNode mutates raw subschemas (deletes the
|
|
20
|
+
* discriminator property from each branch to avoid duplicate rendering).
|
|
21
|
+
* A WeakMap cache would return the pre-mutation deep clone on subsequent
|
|
22
|
+
* mergeAllOf calls, causing deleted properties to reappear inside tab
|
|
23
|
+
* content. Matches prod's per-call behavior.
|
|
24
|
+
*/
|
|
25
|
+
function stripConflictingAdditionalProps(node: any): any {
|
|
26
|
+
if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
|
|
27
|
+
if (!node || typeof node !== "object") return node;
|
|
28
|
+
|
|
29
|
+
let working: any = node;
|
|
30
|
+
if (Array.isArray(node.allOf) && node.allOf.length > 1) {
|
|
31
|
+
const hasStrictMember = node.allOf.some(
|
|
32
|
+
(m: any) => m && m.additionalProperties === false
|
|
33
|
+
);
|
|
34
|
+
if (hasStrictMember) {
|
|
35
|
+
working = {
|
|
36
|
+
...node,
|
|
37
|
+
allOf: node.allOf.map((m: any) => {
|
|
38
|
+
if (m && m.additionalProperties === false) {
|
|
39
|
+
const { additionalProperties: _drop, ...rest } = m;
|
|
40
|
+
return rest;
|
|
41
|
+
}
|
|
42
|
+
return m;
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result: any = {};
|
|
49
|
+
for (const [k, v] of Object.entries(working)) {
|
|
50
|
+
result[k] = stripConflictingAdditionalProps(v);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function mergeAllOf(schema: any): any {
|
|
56
|
+
const onMergeError = (msg: string) => console.warn(msg);
|
|
57
|
+
const merged = merge(stripConflictingAdditionalProps(schema), {
|
|
58
|
+
onMergeError,
|
|
59
|
+
});
|
|
60
|
+
return merged ?? {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Keys that are parent-level metadata and should NOT be folded into branches.
|
|
64
|
+
const METADATA_KEYS = new Set([
|
|
65
|
+
"title",
|
|
66
|
+
"description",
|
|
67
|
+
"discriminator",
|
|
68
|
+
"deprecated",
|
|
69
|
+
"externalDocs",
|
|
70
|
+
"example",
|
|
71
|
+
"examples",
|
|
72
|
+
"xml",
|
|
73
|
+
"nullable",
|
|
74
|
+
"readOnly",
|
|
75
|
+
"writeOnly",
|
|
76
|
+
"default",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fold sibling fields into each oneOf/anyOf branch via allOf-merge so each
|
|
81
|
+
* branch is self-contained. See #1218.
|
|
82
|
+
*
|
|
83
|
+
* Skipped when a discriminator is present: DiscriminatorNode already renders
|
|
84
|
+
* sibling properties at the top level, and folding would duplicate the
|
|
85
|
+
* discriminator metadata into each branch (causing nested DiscriminatorNode
|
|
86
|
+
* renders inside tabs — see #1525 follow-up).
|
|
87
|
+
*
|
|
88
|
+
* Called by normalizeSchema after allOf resolution. Uses mergeAllOf internally
|
|
89
|
+
* to compose `{ allOf: [siblings, branch] }` per branch — the WeakMap caches
|
|
90
|
+
* in stripConflictingAdditionalProps prevent redundant work on shared subtrees.
|
|
91
|
+
*/
|
|
92
|
+
export function foldSiblingsIntoBranches(schema: any): any {
|
|
93
|
+
const branchKey = schema?.oneOf
|
|
94
|
+
? "oneOf"
|
|
95
|
+
: schema?.anyOf
|
|
96
|
+
? "anyOf"
|
|
97
|
+
: undefined;
|
|
98
|
+
if (!branchKey) return schema;
|
|
99
|
+
|
|
100
|
+
const branches = schema[branchKey];
|
|
101
|
+
if (!Array.isArray(branches) || branches.length === 0) return schema;
|
|
102
|
+
|
|
103
|
+
// Discriminator schemas rely on top-level properties being intact so
|
|
104
|
+
// DiscriminatorNode can locate the discriminator property and render
|
|
105
|
+
// shared siblings at the top level. Leave them alone.
|
|
106
|
+
if (schema.discriminator) return schema;
|
|
107
|
+
|
|
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: any = {};
|
|
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
|
+
|
|
118
|
+
const folded = branches.map((branch: any) =>
|
|
119
|
+
mergeAllOf({ allOf: [siblings, branch] })
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const result: any = { [branchKey]: folded };
|
|
123
|
+
for (const key of Object.keys(schema)) {
|
|
124
|
+
if (key !== branchKey && (METADATA_KEYS.has(key) || key.startsWith("x-"))) {
|
|
125
|
+
result[key] = schema[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// WeakMap caches keyed by object identity — shared-reference subtrees from
|
|
132
|
+
// $RefParser.dereference are looked up once, and cycles short-circuit.
|
|
133
|
+
const discriminatorCache = new WeakMap<object, any | null>();
|
|
134
|
+
const findPropertyCache = new WeakMap<object, Map<string, any>>();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Cached recursive discriminator lookup. Returns O(1) on repeated calls
|
|
138
|
+
* with the same object reference (common after normalizeSchema).
|
|
139
|
+
*/
|
|
140
|
+
const BRANCH_KEYS = ["oneOf", "anyOf", "allOf"] as const;
|
|
141
|
+
|
|
142
|
+
export function getDiscriminator(schema: any): any | undefined {
|
|
143
|
+
if (!schema || typeof schema !== "object") return undefined;
|
|
144
|
+
if (discriminatorCache.has(schema)) {
|
|
145
|
+
const cached = discriminatorCache.get(schema);
|
|
146
|
+
return cached === null ? undefined : cached;
|
|
147
|
+
}
|
|
148
|
+
discriminatorCache.set(schema, null);
|
|
149
|
+
|
|
150
|
+
let result: any | undefined = schema.discriminator;
|
|
151
|
+
if (!result) {
|
|
152
|
+
for (const key of BRANCH_KEYS) {
|
|
153
|
+
const branches = schema[key];
|
|
154
|
+
if (!Array.isArray(branches)) continue;
|
|
155
|
+
for (const branch of branches) {
|
|
156
|
+
result = getDiscriminator(branch);
|
|
157
|
+
if (result) break;
|
|
158
|
+
}
|
|
159
|
+
if (result) break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
discriminatorCache.set(schema, result ?? null);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Cached recursive property lookup. Searches schema.properties first, then
|
|
169
|
+
* into oneOf/anyOf/allOf branches. Returns O(1) on repeated calls with the
|
|
170
|
+
* same (schema, propertyName) pair — replaces the O(subtree) findProperty
|
|
171
|
+
* walk that caused #1525's O(N^2) render cost.
|
|
172
|
+
*/
|
|
173
|
+
export function findPropertyDeep(
|
|
174
|
+
schema: any,
|
|
175
|
+
propertyName: string
|
|
176
|
+
): any | undefined {
|
|
177
|
+
if (!schema || typeof schema !== "object") return undefined;
|
|
178
|
+
let cache = findPropertyCache.get(schema);
|
|
179
|
+
if (cache && cache.has(propertyName)) {
|
|
180
|
+
const cached = cache.get(propertyName);
|
|
181
|
+
return cached === null ? undefined : cached;
|
|
182
|
+
}
|
|
183
|
+
if (!cache) {
|
|
184
|
+
cache = new Map();
|
|
185
|
+
findPropertyCache.set(schema, cache);
|
|
186
|
+
}
|
|
187
|
+
cache.set(propertyName, null);
|
|
188
|
+
|
|
189
|
+
let result: any | undefined = schema.properties?.[propertyName];
|
|
190
|
+
if (!result) {
|
|
191
|
+
for (const key of BRANCH_KEYS) {
|
|
192
|
+
const branches = schema[key];
|
|
193
|
+
if (!Array.isArray(branches)) continue;
|
|
194
|
+
for (const branch of branches) {
|
|
195
|
+
result = findPropertyDeep(branch, propertyName);
|
|
196
|
+
if (result) break;
|
|
197
|
+
}
|
|
198
|
+
if (result) break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
cache.set(propertyName, result ?? null);
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Identity-stable schema pass-through. Returns the input as-is so SchemaNode
|
|
208
|
+
* useMemo dependencies stay stable across renders.
|
|
209
|
+
*
|
|
210
|
+
* Earlier iterations of this function eagerly merged allOf and folded
|
|
211
|
+
* siblings into oneOf/anyOf branches, but those operations need to stay
|
|
212
|
+
* conditional inside SchemaNode and DiscriminatorNode — eager versions
|
|
213
|
+
* caused cartesian product expansion (allOf with multiple oneOfs) and
|
|
214
|
+
* double-folding regressions. The render-time helpers below
|
|
215
|
+
* (mergeAllOf, foldSiblingsIntoBranches) are still cached via the WeakMap
|
|
216
|
+
* inside stripConflictingAdditionalProps, so per-render cost stays bounded.
|
|
217
|
+
*
|
|
218
|
+
* The perf win for #1525 comes from getDiscriminator's WeakMap cache
|
|
219
|
+
* replacing the O(subtree) findDiscriminator walk, not from eager
|
|
220
|
+
* normalization here.
|
|
221
|
+
*/
|
|
222
|
+
export function normalizeSchema(schema: any): any {
|
|
223
|
+
return schema;
|
|
224
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.ts","./src/plugin-content-docs.d.ts","./src/postman-code-generators.d.ts","./src/react-magic-dropzone.d.ts","./src/theme-classic.d.ts","./src/theme-openapi.d.ts","./src/types.d.ts","./src/markdown/createDescription.ts","./src/markdown/schema.test.ts","./src/markdown/schema.ts","./src/markdown/utils.test.ts","./src/markdown/utils.ts","./src/theme/translationIds.ts","./src/theme/ApiExplorer/buildPostmanRequest.ts","./src/theme/ApiExplorer/index.tsx","./src/theme/ApiExplorer/persistenceMiddleware.ts","./src/theme/ApiExplorer/storage-utils.ts","./src/theme/ApiExplorer/Accept/index.tsx","./src/theme/ApiExplorer/Accept/slice.ts","./src/theme/ApiExplorer/ApiCodeBlock/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Container/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Content/Element.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Content/String.tsx","./src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Line/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/WordWrapButton/index.tsx","./src/theme/ApiExplorer/Authorization/auth-types.ts","./src/theme/ApiExplorer/Authorization/index.tsx","./src/theme/ApiExplorer/Authorization/slice.ts","./src/theme/ApiExplorer/Body/index.tsx","./src/theme/ApiExplorer/Body/json2xml.d.ts","./src/theme/ApiExplorer/Body/resolveSchemaWithSelections.ts","./src/theme/ApiExplorer/Body/slice.ts","./src/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.tsx","./src/theme/ApiExplorer/Body/FormBodyItem/index.tsx","./src/theme/ApiExplorer/CodeSnippets/code-snippets-types.ts","./src/theme/ApiExplorer/CodeSnippets/index.tsx","./src/theme/ApiExplorer/CodeSnippets/languages.test.ts","./src/theme/ApiExplorer/CodeSnippets/languages.ts","./src/theme/ApiExplorer/CodeTabs/index.tsx","./src/theme/ApiExplorer/ContentType/index.tsx","./src/theme/ApiExplorer/ContentType/slice.ts","./src/theme/ApiExplorer/EncodingSelection/slice.ts","./src/theme/ApiExplorer/EncodingSelection/useResolvedEncoding.ts","./src/theme/ApiExplorer/Export/index.tsx","./src/theme/ApiExplorer/FloatingButton/index.tsx","./src/theme/ApiExplorer/FormFileUpload/index.tsx","./src/theme/ApiExplorer/FormItem/index.tsx","./src/theme/ApiExplorer/FormLabel/index.tsx","./src/theme/ApiExplorer/FormMultiSelect/index.tsx","./src/theme/ApiExplorer/FormSelect/index.tsx","./src/theme/ApiExplorer/FormTextInput/index.tsx","./src/theme/ApiExplorer/LiveEditor/index.tsx","./src/theme/ApiExplorer/MethodEndpoint/index.tsx","./src/theme/ApiExplorer/ParamOptions/index.tsx","./src/theme/ApiExplorer/ParamOptions/slice.ts","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.tsx","./src/theme/ApiExplorer/Request/index.tsx","./src/theme/ApiExplorer/Request/makeRequest.ts","./src/theme/ApiExplorer/Response/index.tsx","./src/theme/ApiExplorer/Response/slice.ts","./src/theme/ApiExplorer/SchemaSelection/index.ts","./src/theme/ApiExplorer/SchemaSelection/slice.ts","./src/theme/ApiExplorer/SecuritySchemes/index.tsx","./src/theme/ApiExplorer/Server/index.tsx","./src/theme/ApiExplorer/Server/slice.ts","./src/theme/ApiItem/hooks.ts","./src/theme/ApiItem/index.tsx","./src/theme/ApiItem/store.ts","./src/theme/ApiItem/Layout/index.tsx","./src/theme/ApiLogo/index.tsx","./src/theme/ApiTabs/index.tsx","./src/theme/ArrayBrackets/index.tsx","./src/theme/CodeSamples/index.tsx","./src/theme/DiscriminatorTabs/index.tsx","./src/theme/Example/index.tsx","./src/theme/Markdown/index.d.ts","./src/theme/MimeTabs/index.tsx","./src/theme/OperationTabs/index.tsx","./src/theme/ParamsDetails/index.tsx","./src/theme/ParamsItem/index.tsx","./src/theme/RequestSchema/index.tsx","./src/theme/ResponseExamples/index.tsx","./src/theme/ResponseHeaders/index.tsx","./src/theme/ResponseSchema/index.tsx","./src/theme/Schema/index.tsx","./src/theme/SchemaExpansion/context.tsx","./src/theme/SchemaExpansion/index.tsx","./src/theme/SchemaItem/index.tsx","./src/theme/SchemaTabs/index.tsx","./src/theme/SkeletonLoader/index.tsx","./src/theme/StatusCodes/index.tsx","./src/theme/TabItem/index.tsx","./src/theme/Tabs/index.tsx","./src/theme/utils/codeBlockUtils.ts","./src/theme/utils/reactUtils.ts","./src/theme/utils/scrollUtils.tsx","./src/theme/utils/tabsUtils.tsx","./src/theme/utils/useCodeWordWrap.ts","./src/theme/utils/useMutationObserver.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.ts","./src/plugin-content-docs.d.ts","./src/postman-code-generators.d.ts","./src/react-magic-dropzone.d.ts","./src/theme-classic.d.ts","./src/theme-openapi.d.ts","./src/types.d.ts","./src/markdown/createDescription.ts","./src/markdown/schema.test.ts","./src/markdown/schema.ts","./src/markdown/utils.test.ts","./src/markdown/utils.ts","./src/theme/translationIds.ts","./src/theme/ApiExplorer/buildPostmanRequest.ts","./src/theme/ApiExplorer/index.tsx","./src/theme/ApiExplorer/persistenceMiddleware.ts","./src/theme/ApiExplorer/storage-utils.ts","./src/theme/ApiExplorer/Accept/index.tsx","./src/theme/ApiExplorer/Accept/slice.ts","./src/theme/ApiExplorer/ApiCodeBlock/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Container/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Content/Element.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Content/String.tsx","./src/theme/ApiExplorer/ApiCodeBlock/CopyButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/ExitButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/ExpandButton/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/Line/index.tsx","./src/theme/ApiExplorer/ApiCodeBlock/WordWrapButton/index.tsx","./src/theme/ApiExplorer/Authorization/auth-types.ts","./src/theme/ApiExplorer/Authorization/index.tsx","./src/theme/ApiExplorer/Authorization/slice.ts","./src/theme/ApiExplorer/Body/index.tsx","./src/theme/ApiExplorer/Body/json2xml.d.ts","./src/theme/ApiExplorer/Body/resolveSchemaWithSelections.ts","./src/theme/ApiExplorer/Body/slice.ts","./src/theme/ApiExplorer/Body/FileArrayFormBodyItem/index.tsx","./src/theme/ApiExplorer/Body/FormBodyItem/index.tsx","./src/theme/ApiExplorer/CodeSnippets/code-snippets-types.ts","./src/theme/ApiExplorer/CodeSnippets/index.tsx","./src/theme/ApiExplorer/CodeSnippets/languages.test.ts","./src/theme/ApiExplorer/CodeSnippets/languages.ts","./src/theme/ApiExplorer/CodeTabs/index.tsx","./src/theme/ApiExplorer/ContentType/index.tsx","./src/theme/ApiExplorer/ContentType/slice.ts","./src/theme/ApiExplorer/EncodingSelection/slice.ts","./src/theme/ApiExplorer/EncodingSelection/useResolvedEncoding.ts","./src/theme/ApiExplorer/Export/index.tsx","./src/theme/ApiExplorer/FloatingButton/index.tsx","./src/theme/ApiExplorer/FormFileUpload/index.tsx","./src/theme/ApiExplorer/FormItem/index.tsx","./src/theme/ApiExplorer/FormLabel/index.tsx","./src/theme/ApiExplorer/FormMultiSelect/index.tsx","./src/theme/ApiExplorer/FormSelect/index.tsx","./src/theme/ApiExplorer/FormTextInput/index.tsx","./src/theme/ApiExplorer/LiveEditor/index.tsx","./src/theme/ApiExplorer/MethodEndpoint/index.tsx","./src/theme/ApiExplorer/ParamOptions/index.tsx","./src/theme/ApiExplorer/ParamOptions/slice.ts","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx","./src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamTextFormItem.tsx","./src/theme/ApiExplorer/Request/index.tsx","./src/theme/ApiExplorer/Request/makeRequest.ts","./src/theme/ApiExplorer/Response/index.tsx","./src/theme/ApiExplorer/Response/slice.ts","./src/theme/ApiExplorer/SchemaSelection/index.ts","./src/theme/ApiExplorer/SchemaSelection/slice.ts","./src/theme/ApiExplorer/SecuritySchemes/index.tsx","./src/theme/ApiExplorer/Server/index.tsx","./src/theme/ApiExplorer/Server/slice.ts","./src/theme/ApiItem/hooks.ts","./src/theme/ApiItem/index.tsx","./src/theme/ApiItem/store.ts","./src/theme/ApiItem/Layout/index.tsx","./src/theme/ApiLogo/index.tsx","./src/theme/ApiTabs/index.tsx","./src/theme/ArrayBrackets/index.tsx","./src/theme/CodeSamples/index.tsx","./src/theme/DiscriminatorTabs/index.tsx","./src/theme/Example/index.tsx","./src/theme/Markdown/index.d.ts","./src/theme/MimeTabs/index.tsx","./src/theme/OperationTabs/index.tsx","./src/theme/ParamsDetails/index.tsx","./src/theme/ParamsItem/index.tsx","./src/theme/RequestSchema/index.tsx","./src/theme/ResponseExamples/index.tsx","./src/theme/ResponseHeaders/index.tsx","./src/theme/ResponseSchema/index.tsx","./src/theme/Schema/index.tsx","./src/theme/Schema/normalize.test.ts","./src/theme/Schema/normalize.ts","./src/theme/SchemaExpansion/context.tsx","./src/theme/SchemaExpansion/index.tsx","./src/theme/SchemaItem/index.tsx","./src/theme/SchemaTabs/index.tsx","./src/theme/SkeletonLoader/index.tsx","./src/theme/StatusCodes/index.tsx","./src/theme/TabItem/index.tsx","./src/theme/Tabs/index.tsx","./src/theme/utils/codeBlockUtils.ts","./src/theme/utils/reactUtils.ts","./src/theme/utils/scrollUtils.tsx","./src/theme/utils/tabsUtils.tsx","./src/theme/utils/useCodeWordWrap.ts","./src/theme/utils/useMutationObserver.ts"],"version":"5.9.3"}
|