docusaurus-theme-openapi-docs 4.6.0 → 4.7.0
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/markdown/schema.js +5 -0
- package/lib/theme/ApiExplorer/Body/index.js +263 -128
- package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.d.ts +13 -0
- package/lib/theme/ApiExplorer/Body/resolveSchemaWithSelections.js +133 -0
- package/lib/theme/ApiExplorer/SchemaSelection/index.d.ts +2 -0
- package/lib/theme/ApiExplorer/SchemaSelection/index.js +36 -0
- package/lib/theme/ApiExplorer/SchemaSelection/slice.d.ts +37 -0
- package/lib/theme/ApiExplorer/SchemaSelection/slice.js +39 -0
- package/lib/theme/ApiExplorer/persistenceMiddleware.d.ts +2 -0
- package/lib/theme/ApiItem/hooks.d.ts +1 -0
- package/lib/theme/ApiItem/index.js +1 -0
- package/lib/theme/ApiItem/store.d.ts +6 -0
- package/lib/theme/ApiItem/store.js +6 -2
- package/lib/theme/RequestSchema/index.js +58 -52
- package/lib/theme/Schema/index.d.ts +6 -0
- package/lib/theme/Schema/index.js +135 -9
- package/lib/theme/SchemaTabs/index.d.ts +8 -1
- package/lib/theme/SchemaTabs/index.js +10 -1
- package/lib/theme/StatusCodes/index.d.ts +1 -1
- package/lib/theme/styles.scss +10 -0
- package/package.json +3 -3
- package/src/markdown/schema.ts +6 -0
- package/src/theme/ApiExplorer/Body/index.tsx +206 -122
- package/src/theme/ApiExplorer/Body/resolveSchemaWithSelections.ts +155 -0
- package/{lib/types.js → src/theme/ApiExplorer/SchemaSelection/index.ts} +7 -2
- package/src/theme/ApiExplorer/SchemaSelection/slice.ts +46 -0
- package/src/theme/ApiItem/index.tsx +1 -0
- package/src/theme/ApiItem/store.ts +2 -0
- package/src/theme/RequestSchema/index.tsx +49 -39
- package/src/theme/Schema/index.tsx +184 -27
- package/src/theme/SchemaTabs/index.tsx +15 -4
- package/src/theme/StatusCodes/index.tsx +1 -2
- package/src/theme/styles.scss +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/types.d.ts +0 -54
- /package/src/{types.ts → types.d.ts} +0 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
|
-
import React from "react";
|
|
8
|
+
import React, { useEffect, useMemo } from "react";
|
|
9
9
|
|
|
10
10
|
import { translate } from "@docusaurus/Translate";
|
|
11
11
|
|
|
@@ -19,10 +19,12 @@ import SchemaTabs from "@theme/SchemaTabs";
|
|
|
19
19
|
import TabItem from "@theme/TabItem";
|
|
20
20
|
import { OPENAPI_BODY, OPENAPI_REQUEST } from "@theme/translationIds";
|
|
21
21
|
import { RequestBodyObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
|
|
22
|
+
import { sampleFromSchema } from "docusaurus-plugin-openapi-docs/src/openapi/createSchemaExample";
|
|
22
23
|
import format from "xml-formatter";
|
|
23
24
|
|
|
24
25
|
import { clearRawBody, setFileRawBody, setStringRawBody } from "./slice";
|
|
25
26
|
import FormBodyItem from "./FormBodyItem";
|
|
27
|
+
import { resolveSchemaWithSelections } from "./resolveSchemaWithSelections";
|
|
26
28
|
|
|
27
29
|
export interface Props {
|
|
28
30
|
jsonRequestBodyExample: string;
|
|
@@ -63,6 +65,9 @@ function Body({
|
|
|
63
65
|
required,
|
|
64
66
|
}: Props) {
|
|
65
67
|
const contentType = useTypedSelector((state: any) => state.contentType.value);
|
|
68
|
+
const schemaSelections = useTypedSelector(
|
|
69
|
+
(state: any) => state.schemaSelection?.selections ?? {}
|
|
70
|
+
);
|
|
66
71
|
const dispatch = useTypedDispatch();
|
|
67
72
|
|
|
68
73
|
// Lot's of possible content-types:
|
|
@@ -86,12 +91,200 @@ function Body({
|
|
|
86
91
|
// - multipart/form-data
|
|
87
92
|
// - application/x-www-form-urlencoded
|
|
88
93
|
|
|
89
|
-
const
|
|
94
|
+
const rawSchema = requestBodyMetadata?.content?.[contentType]?.schema;
|
|
90
95
|
const example = requestBodyMetadata?.content?.[contentType]?.example;
|
|
91
96
|
const examples = requestBodyMetadata?.content?.[contentType]?.examples;
|
|
97
|
+
|
|
98
|
+
// Resolve the schema based on user's anyOf/oneOf tab selections
|
|
99
|
+
const schema = useMemo(() => {
|
|
100
|
+
if (!rawSchema) return rawSchema;
|
|
101
|
+
return resolveSchemaWithSelections(
|
|
102
|
+
rawSchema,
|
|
103
|
+
schemaSelections,
|
|
104
|
+
"requestBody"
|
|
105
|
+
);
|
|
106
|
+
}, [rawSchema, schemaSelections]);
|
|
107
|
+
|
|
92
108
|
// OpenAPI 3.1 / JSON Schema: schema.examples is an array of example values
|
|
93
109
|
const schemaExamples = schema?.examples as any[] | undefined;
|
|
94
110
|
|
|
111
|
+
// Compute the default body based on content type and schema
|
|
112
|
+
// This needs to be computed before early returns so the useEffect can use it
|
|
113
|
+
const { defaultBody, exampleBody, examplesBodies, language } = useMemo(() => {
|
|
114
|
+
let lang = "plaintext";
|
|
115
|
+
let defBody = "";
|
|
116
|
+
let exBody;
|
|
117
|
+
let exBodies = [] as any;
|
|
118
|
+
|
|
119
|
+
// Skip body generation for binary and form content types
|
|
120
|
+
if (schema?.format === "binary") {
|
|
121
|
+
return {
|
|
122
|
+
defaultBody: defBody,
|
|
123
|
+
exampleBody: exBody,
|
|
124
|
+
examplesBodies: exBodies,
|
|
125
|
+
language: lang,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
(contentType === "multipart/form-data" ||
|
|
130
|
+
contentType === "application/x-www-form-urlencoded") &&
|
|
131
|
+
schema?.type === "object"
|
|
132
|
+
) {
|
|
133
|
+
return {
|
|
134
|
+
defaultBody: defBody,
|
|
135
|
+
exampleBody: exBody,
|
|
136
|
+
examplesBodies: exBodies,
|
|
137
|
+
language: lang,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Generate example from the schema for the current content type
|
|
142
|
+
let contentTypeExample;
|
|
143
|
+
if (schema) {
|
|
144
|
+
contentTypeExample = sampleFromSchema(schema, { type: "request" });
|
|
145
|
+
} else if (jsonRequestBodyExample) {
|
|
146
|
+
// Fallback to the build-time generated example if no schema is available
|
|
147
|
+
contentTypeExample = jsonRequestBodyExample;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
contentType?.includes("application/json") ||
|
|
152
|
+
contentType?.endsWith("+json")
|
|
153
|
+
) {
|
|
154
|
+
if (contentTypeExample) {
|
|
155
|
+
defBody = JSON.stringify(contentTypeExample, null, 2);
|
|
156
|
+
}
|
|
157
|
+
if (example) {
|
|
158
|
+
exBody = JSON.stringify(example, null, 2);
|
|
159
|
+
}
|
|
160
|
+
if (examples) {
|
|
161
|
+
for (const [key, ex] of Object.entries(examples)) {
|
|
162
|
+
let body = ex.value;
|
|
163
|
+
try {
|
|
164
|
+
// If the value is already valid JSON we shouldn't double encode the value
|
|
165
|
+
JSON.parse(ex.value);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
body = JSON.stringify(ex.value, null, 2);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
exBodies.push({
|
|
171
|
+
label: key,
|
|
172
|
+
body,
|
|
173
|
+
summary: ex.summary,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// OpenAPI 3.1: schema.examples is an array of example values
|
|
178
|
+
if (schemaExamples && Array.isArray(schemaExamples)) {
|
|
179
|
+
schemaExamples.forEach((schemaExample, index) => {
|
|
180
|
+
const body = JSON.stringify(schemaExample, null, 2);
|
|
181
|
+
exBodies.push({
|
|
182
|
+
label: `Example ${index + 1}`,
|
|
183
|
+
body,
|
|
184
|
+
summary: undefined,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
lang = "json";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (contentType === "application/xml" || contentType?.endsWith("+xml")) {
|
|
192
|
+
if (contentTypeExample) {
|
|
193
|
+
try {
|
|
194
|
+
defBody = format(json2xml(contentTypeExample, ""), {
|
|
195
|
+
indentation: " ",
|
|
196
|
+
lineSeparator: "\n",
|
|
197
|
+
collapseContent: true,
|
|
198
|
+
});
|
|
199
|
+
} catch {
|
|
200
|
+
defBody = json2xml(contentTypeExample);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (example) {
|
|
204
|
+
try {
|
|
205
|
+
exBody = format(json2xml(example, ""), {
|
|
206
|
+
indentation: " ",
|
|
207
|
+
lineSeparator: "\n",
|
|
208
|
+
collapseContent: true,
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
exBody = json2xml(example);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (examples) {
|
|
215
|
+
for (const [key, ex] of Object.entries(examples)) {
|
|
216
|
+
let formattedXmlBody;
|
|
217
|
+
try {
|
|
218
|
+
formattedXmlBody = format(ex.value, {
|
|
219
|
+
indentation: " ",
|
|
220
|
+
lineSeparator: "\n",
|
|
221
|
+
collapseContent: true,
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
formattedXmlBody = ex.value;
|
|
225
|
+
}
|
|
226
|
+
exBodies.push({
|
|
227
|
+
label: key,
|
|
228
|
+
body: formattedXmlBody,
|
|
229
|
+
summary: ex.summary,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// OpenAPI 3.1: schema.examples is an array of example values
|
|
234
|
+
if (schemaExamples && Array.isArray(schemaExamples)) {
|
|
235
|
+
schemaExamples.forEach((schemaExample, index) => {
|
|
236
|
+
let formattedXmlBody;
|
|
237
|
+
try {
|
|
238
|
+
formattedXmlBody = format(json2xml(schemaExample, ""), {
|
|
239
|
+
indentation: " ",
|
|
240
|
+
lineSeparator: "\n",
|
|
241
|
+
collapseContent: true,
|
|
242
|
+
});
|
|
243
|
+
} catch {
|
|
244
|
+
formattedXmlBody = json2xml(schemaExample);
|
|
245
|
+
}
|
|
246
|
+
exBodies.push({
|
|
247
|
+
label: `Example ${index + 1}`,
|
|
248
|
+
body: formattedXmlBody,
|
|
249
|
+
summary: undefined,
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
lang = "xml";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
defaultBody: defBody,
|
|
258
|
+
exampleBody: exBody,
|
|
259
|
+
examplesBodies: exBodies,
|
|
260
|
+
language: lang,
|
|
261
|
+
};
|
|
262
|
+
}, [
|
|
263
|
+
schema,
|
|
264
|
+
contentType,
|
|
265
|
+
example,
|
|
266
|
+
examples,
|
|
267
|
+
schemaExamples,
|
|
268
|
+
jsonRequestBodyExample,
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
// Create a stable key for the LiveApp component that changes when schema selection changes
|
|
272
|
+
// This forces the editor to remount and pick up the new defaultBody
|
|
273
|
+
const schemaSelectionKey = useMemo(
|
|
274
|
+
() => JSON.stringify(schemaSelections),
|
|
275
|
+
[schemaSelections]
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Update body in Redux when content type or schema selection changes
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
if (defaultBody) {
|
|
281
|
+
dispatch(setStringRawBody(defaultBody));
|
|
282
|
+
}
|
|
283
|
+
// Re-run when contentType, schemaSelections, or defaultBody change
|
|
284
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
285
|
+
}, [contentType, schemaSelections, defaultBody]);
|
|
286
|
+
|
|
287
|
+
// Now handle early returns after all hooks have been called
|
|
95
288
|
if (schema?.format === "binary") {
|
|
96
289
|
return (
|
|
97
290
|
<FormItem>
|
|
@@ -116,6 +309,7 @@ function Body({
|
|
|
116
309
|
</FormItem>
|
|
117
310
|
);
|
|
118
311
|
}
|
|
312
|
+
|
|
119
313
|
if (
|
|
120
314
|
(contentType === "multipart/form-data" ||
|
|
121
315
|
contentType === "application/x-www-form-urlencoded") &&
|
|
@@ -144,117 +338,6 @@ function Body({
|
|
|
144
338
|
);
|
|
145
339
|
}
|
|
146
340
|
|
|
147
|
-
let language = "plaintext";
|
|
148
|
-
let defaultBody = ""; //"body content";
|
|
149
|
-
let exampleBody;
|
|
150
|
-
let examplesBodies = [] as any;
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
contentType.includes("application/json") ||
|
|
154
|
-
contentType.endsWith("+json")
|
|
155
|
-
) {
|
|
156
|
-
if (jsonRequestBodyExample) {
|
|
157
|
-
defaultBody = JSON.stringify(jsonRequestBodyExample, null, 2);
|
|
158
|
-
}
|
|
159
|
-
if (example) {
|
|
160
|
-
exampleBody = JSON.stringify(example, null, 2);
|
|
161
|
-
}
|
|
162
|
-
if (examples) {
|
|
163
|
-
for (const [key, example] of Object.entries(examples)) {
|
|
164
|
-
let body = example.value;
|
|
165
|
-
try {
|
|
166
|
-
// If the value is already valid JSON we shouldn't double encode the value
|
|
167
|
-
JSON.parse(example.value);
|
|
168
|
-
} catch (e) {
|
|
169
|
-
body = JSON.stringify(example.value, null, 2);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
examplesBodies.push({
|
|
173
|
-
label: key,
|
|
174
|
-
body,
|
|
175
|
-
summary: example.summary,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// OpenAPI 3.1: schema.examples is an array of example values
|
|
180
|
-
if (schemaExamples && Array.isArray(schemaExamples)) {
|
|
181
|
-
schemaExamples.forEach((schemaExample, index) => {
|
|
182
|
-
const body = JSON.stringify(schemaExample, null, 2);
|
|
183
|
-
examplesBodies.push({
|
|
184
|
-
label: `Example ${index + 1}`,
|
|
185
|
-
body,
|
|
186
|
-
summary: undefined,
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
language = "json";
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (contentType === "application/xml" || contentType.endsWith("+xml")) {
|
|
194
|
-
if (jsonRequestBodyExample) {
|
|
195
|
-
try {
|
|
196
|
-
defaultBody = format(json2xml(jsonRequestBodyExample, ""), {
|
|
197
|
-
indentation: " ",
|
|
198
|
-
lineSeparator: "\n",
|
|
199
|
-
collapseContent: true,
|
|
200
|
-
});
|
|
201
|
-
} catch {
|
|
202
|
-
defaultBody = json2xml(jsonRequestBodyExample);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (example) {
|
|
206
|
-
try {
|
|
207
|
-
exampleBody = format(json2xml(example, ""), {
|
|
208
|
-
indentation: " ",
|
|
209
|
-
lineSeparator: "\n",
|
|
210
|
-
collapseContent: true,
|
|
211
|
-
});
|
|
212
|
-
} catch {
|
|
213
|
-
exampleBody = json2xml(example);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (examples) {
|
|
217
|
-
for (const [key, example] of Object.entries(examples)) {
|
|
218
|
-
let formattedXmlBody;
|
|
219
|
-
try {
|
|
220
|
-
formattedXmlBody = format(example.value, {
|
|
221
|
-
indentation: " ",
|
|
222
|
-
lineSeparator: "\n",
|
|
223
|
-
collapseContent: true,
|
|
224
|
-
});
|
|
225
|
-
} catch {
|
|
226
|
-
formattedXmlBody = example.value;
|
|
227
|
-
}
|
|
228
|
-
examplesBodies.push({
|
|
229
|
-
label: key,
|
|
230
|
-
body: formattedXmlBody,
|
|
231
|
-
summary: example.summary,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
// OpenAPI 3.1: schema.examples is an array of example values
|
|
236
|
-
if (schemaExamples && Array.isArray(schemaExamples)) {
|
|
237
|
-
schemaExamples.forEach((schemaExample, index) => {
|
|
238
|
-
let formattedXmlBody;
|
|
239
|
-
try {
|
|
240
|
-
formattedXmlBody = format(json2xml(schemaExample, ""), {
|
|
241
|
-
indentation: " ",
|
|
242
|
-
lineSeparator: "\n",
|
|
243
|
-
collapseContent: true,
|
|
244
|
-
});
|
|
245
|
-
} catch {
|
|
246
|
-
formattedXmlBody = json2xml(schemaExample);
|
|
247
|
-
}
|
|
248
|
-
examplesBodies.push({
|
|
249
|
-
label: `Example ${index + 1}`,
|
|
250
|
-
body: formattedXmlBody,
|
|
251
|
-
summary: undefined,
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
language = "xml";
|
|
256
|
-
}
|
|
257
|
-
|
|
258
341
|
if (exampleBody) {
|
|
259
342
|
return (
|
|
260
343
|
<FormItem>
|
|
@@ -269,6 +352,7 @@ function Body({
|
|
|
269
352
|
default
|
|
270
353
|
>
|
|
271
354
|
<LiveApp
|
|
355
|
+
key={`${contentType}-${schemaSelectionKey}`}
|
|
272
356
|
action={(code: string) => dispatch(setStringRawBody(code))}
|
|
273
357
|
language={language}
|
|
274
358
|
required={required}
|
|
@@ -281,6 +365,7 @@ function Body({
|
|
|
281
365
|
{example.summary && <Markdown>{example.summary}</Markdown>}
|
|
282
366
|
{exampleBody && (
|
|
283
367
|
<LiveApp
|
|
368
|
+
key={`${contentType}-example`}
|
|
284
369
|
action={(code: string) => dispatch(setStringRawBody(code))}
|
|
285
370
|
language={language}
|
|
286
371
|
required={required}
|
|
@@ -308,6 +393,7 @@ function Body({
|
|
|
308
393
|
default
|
|
309
394
|
>
|
|
310
395
|
<LiveApp
|
|
396
|
+
key={`${contentType}-${schemaSelectionKey}`}
|
|
311
397
|
action={(code: string) => dispatch(setStringRawBody(code))}
|
|
312
398
|
language={language}
|
|
313
399
|
required={required}
|
|
@@ -315,21 +401,18 @@ function Body({
|
|
|
315
401
|
{defaultBody}
|
|
316
402
|
</LiveApp>
|
|
317
403
|
</TabItem>
|
|
318
|
-
{examplesBodies.map((
|
|
404
|
+
{examplesBodies.map((ex: any) => {
|
|
319
405
|
return (
|
|
320
406
|
// @ts-ignore
|
|
321
|
-
<TabItem
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
key={example.label}
|
|
325
|
-
>
|
|
326
|
-
{example.summary && <Markdown>{example.summary}</Markdown>}
|
|
327
|
-
{example.body && (
|
|
407
|
+
<TabItem label={ex.label} value={ex.label} key={ex.label}>
|
|
408
|
+
{ex.summary && <Markdown>{ex.summary}</Markdown>}
|
|
409
|
+
{ex.body && (
|
|
328
410
|
<LiveApp
|
|
411
|
+
key={`${contentType}-${ex.label}`}
|
|
329
412
|
action={(code: string) => dispatch(setStringRawBody(code))}
|
|
330
413
|
language={language}
|
|
331
414
|
>
|
|
332
|
-
{
|
|
415
|
+
{ex.body}
|
|
333
416
|
</LiveApp>
|
|
334
417
|
)}
|
|
335
418
|
</TabItem>
|
|
@@ -343,6 +426,7 @@ function Body({
|
|
|
343
426
|
return (
|
|
344
427
|
<FormItem>
|
|
345
428
|
<LiveApp
|
|
429
|
+
key={`${contentType}-${schemaSelectionKey}`}
|
|
346
430
|
action={(code: string) => dispatch(setStringRawBody(code))}
|
|
347
431
|
language={language}
|
|
348
432
|
required={required}
|
|
@@ -0,0 +1,155 @@
|
|
|
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 { SchemaObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
|
|
9
|
+
import merge from "lodash/merge";
|
|
10
|
+
|
|
11
|
+
export interface SchemaSelections {
|
|
12
|
+
[schemaPath: string]: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a schema by replacing anyOf/oneOf with the selected option based on user selections.
|
|
17
|
+
*
|
|
18
|
+
* @param schema - The original schema object
|
|
19
|
+
* @param selections - Map of schema paths to selected indices
|
|
20
|
+
* @param basePath - The base path for this schema (used for looking up selections)
|
|
21
|
+
* @returns A new schema with anyOf/oneOf resolved to selected options
|
|
22
|
+
*/
|
|
23
|
+
export function resolveSchemaWithSelections(
|
|
24
|
+
schema: SchemaObject | undefined,
|
|
25
|
+
selections: SchemaSelections,
|
|
26
|
+
basePath: string = "requestBody"
|
|
27
|
+
): SchemaObject | undefined {
|
|
28
|
+
if (!schema) {
|
|
29
|
+
return schema;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Deep clone to avoid mutating the original schema
|
|
33
|
+
const schemaCopy = JSON.parse(JSON.stringify(schema)) as SchemaObject;
|
|
34
|
+
|
|
35
|
+
return resolveSchemaRecursive(schemaCopy, selections, basePath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveSchemaRecursive(
|
|
39
|
+
schema: SchemaObject,
|
|
40
|
+
selections: SchemaSelections,
|
|
41
|
+
currentPath: string
|
|
42
|
+
): SchemaObject {
|
|
43
|
+
// Handle oneOf
|
|
44
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
45
|
+
const selectedIndex = selections[currentPath] ?? 0;
|
|
46
|
+
const selectedSchema = schema.oneOf[selectedIndex] as SchemaObject;
|
|
47
|
+
|
|
48
|
+
if (selectedSchema) {
|
|
49
|
+
// If there are shared properties, merge them with the selected schema
|
|
50
|
+
if (schema.properties) {
|
|
51
|
+
const mergedSchema = merge({}, schema, selectedSchema);
|
|
52
|
+
delete mergedSchema.oneOf;
|
|
53
|
+
|
|
54
|
+
// Continue resolving nested schemas in the merged result
|
|
55
|
+
return resolveSchemaRecursive(
|
|
56
|
+
mergedSchema,
|
|
57
|
+
selections,
|
|
58
|
+
`${currentPath}.${selectedIndex}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// No shared properties, just use the selected schema
|
|
63
|
+
// Continue resolving in case there are nested anyOf/oneOf
|
|
64
|
+
return resolveSchemaRecursive(
|
|
65
|
+
selectedSchema,
|
|
66
|
+
selections,
|
|
67
|
+
`${currentPath}.${selectedIndex}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle anyOf
|
|
73
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
74
|
+
const selectedIndex = selections[currentPath] ?? 0;
|
|
75
|
+
const selectedSchema = schema.anyOf[selectedIndex] as SchemaObject;
|
|
76
|
+
|
|
77
|
+
if (selectedSchema) {
|
|
78
|
+
// If there are shared properties, merge them with the selected schema
|
|
79
|
+
if (schema.properties) {
|
|
80
|
+
const mergedSchema = merge({}, schema, selectedSchema);
|
|
81
|
+
delete mergedSchema.anyOf;
|
|
82
|
+
|
|
83
|
+
// Continue resolving nested schemas in the merged result
|
|
84
|
+
return resolveSchemaRecursive(
|
|
85
|
+
mergedSchema,
|
|
86
|
+
selections,
|
|
87
|
+
`${currentPath}.${selectedIndex}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// No shared properties, just use the selected schema
|
|
92
|
+
// Continue resolving in case there are nested anyOf/oneOf
|
|
93
|
+
return resolveSchemaRecursive(
|
|
94
|
+
selectedSchema,
|
|
95
|
+
selections,
|
|
96
|
+
`${currentPath}.${selectedIndex}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle allOf - merge all schemas and continue resolving
|
|
102
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
103
|
+
// Process each allOf item, resolving any anyOf/oneOf within them
|
|
104
|
+
const resolvedItems = schema.allOf.map((item, index) => {
|
|
105
|
+
return resolveSchemaRecursive(
|
|
106
|
+
item as SchemaObject,
|
|
107
|
+
selections,
|
|
108
|
+
`${currentPath}.allOf.${index}`
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Merge all resolved items
|
|
113
|
+
const mergedSchema = resolvedItems.reduce(
|
|
114
|
+
(acc, item) => merge(acc, item),
|
|
115
|
+
{} as SchemaObject
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Preserve any top-level properties from the original schema
|
|
119
|
+
if (schema.properties) {
|
|
120
|
+
mergedSchema.properties = merge(
|
|
121
|
+
{},
|
|
122
|
+
mergedSchema.properties,
|
|
123
|
+
schema.properties
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return mergedSchema;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle object properties recursively
|
|
131
|
+
if (schema.properties) {
|
|
132
|
+
const resolvedProperties: { [key: string]: SchemaObject } = {};
|
|
133
|
+
|
|
134
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
135
|
+
resolvedProperties[propName] = resolveSchemaRecursive(
|
|
136
|
+
propSchema as SchemaObject,
|
|
137
|
+
selections,
|
|
138
|
+
`${currentPath}.${propName}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
schema.properties = resolvedProperties;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle array items recursively
|
|
146
|
+
if (schema.items) {
|
|
147
|
+
schema.items = resolveSchemaRecursive(
|
|
148
|
+
schema.items as SchemaObject,
|
|
149
|
+
selections,
|
|
150
|
+
`${currentPath}.items`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return schema;
|
|
155
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* ============================================================================
|
|
3
2
|
* Copyright (c) Palo Alto Networks
|
|
4
3
|
*
|
|
5
4
|
* This source code is licensed under the MIT license found in the
|
|
6
5
|
* LICENSE file in the root directory of this source tree.
|
|
7
6
|
* ========================================================================== */
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
default as schemaSelectionReducer,
|
|
10
|
+
setSchemaSelection,
|
|
11
|
+
clearSchemaSelections,
|
|
12
|
+
} from "./slice";
|
|
13
|
+
export type { SchemaSelectionState } from "./slice";
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
9
|
+
|
|
10
|
+
export interface SchemaSelectionState {
|
|
11
|
+
/**
|
|
12
|
+
* Maps schema path (e.g., "requestBody", "requestBody.anyOf.0.layer3")
|
|
13
|
+
* to the selected anyOf/oneOf option index
|
|
14
|
+
*/
|
|
15
|
+
selections: { [schemaPath: string]: number };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const initialState: SchemaSelectionState = {
|
|
19
|
+
selections: {},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const slice = createSlice({
|
|
23
|
+
name: "schemaSelection",
|
|
24
|
+
initialState,
|
|
25
|
+
reducers: {
|
|
26
|
+
/**
|
|
27
|
+
* Set the selected index for a specific schema path
|
|
28
|
+
*/
|
|
29
|
+
setSchemaSelection: (
|
|
30
|
+
state,
|
|
31
|
+
action: PayloadAction<{ path: string; index: number }>
|
|
32
|
+
) => {
|
|
33
|
+
state.selections[action.payload.path] = action.payload.index;
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Clear all schema selections (useful when navigating to a new API endpoint)
|
|
37
|
+
*/
|
|
38
|
+
clearSchemaSelections: (state) => {
|
|
39
|
+
state.selections = {};
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const { setSchemaSelection, clearSchemaSelections } = slice.actions;
|
|
45
|
+
|
|
46
|
+
export default slice.reducer;
|
|
@@ -12,6 +12,7 @@ import body from "@theme/ApiExplorer/Body/slice";
|
|
|
12
12
|
import contentType from "@theme/ApiExplorer/ContentType/slice";
|
|
13
13
|
import params from "@theme/ApiExplorer/ParamOptions/slice";
|
|
14
14
|
import response from "@theme/ApiExplorer/Response/slice";
|
|
15
|
+
import schemaSelection from "@theme/ApiExplorer/SchemaSelection/slice";
|
|
15
16
|
import server from "@theme/ApiExplorer/Server/slice";
|
|
16
17
|
|
|
17
18
|
const rootReducer = combineReducers({
|
|
@@ -22,6 +23,7 @@ const rootReducer = combineReducers({
|
|
|
22
23
|
body,
|
|
23
24
|
params,
|
|
24
25
|
auth,
|
|
26
|
+
schemaSelection,
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
export type RootState = ReturnType<typeof rootReducer>;
|