docusaurus-plugin-openapi-docs 1.0.6 → 1.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/README.md +1 -2
- package/lib/markdown/createSchemaDetails.js +325 -132
- package/lib/markdown/index.js +1 -0
- package/lib/markdown/schema.js +25 -9
- package/lib/markdown/utils.d.ts +1 -1
- package/lib/markdown/utils.js +4 -1
- package/lib/openapi/openapi.d.ts +3 -3
- package/lib/openapi/openapi.js +30 -26
- package/lib/openapi/types.d.ts +2 -1
- package/lib/openapi/utils/loadAndResolveSpec.d.ts +2 -0
- package/lib/openapi/utils/{loadAndBundleSpec.js → loadAndResolveSpec.js} +61 -28
- package/lib/openapi/utils/services/OpenAPIParser.d.ts +52 -0
- package/lib/openapi/utils/services/OpenAPIParser.js +342 -0
- package/lib/openapi/utils/services/RedocNormalizedOptions.d.ts +100 -0
- package/lib/openapi/utils/services/RedocNormalizedOptions.js +170 -0
- package/lib/openapi/utils/types/index.d.ts +2 -0
- package/lib/openapi/utils/types/index.js +23 -0
- package/lib/openapi/utils/types/open-api.d.ts +305 -0
- package/lib/openapi/utils/types/open-api.js +8 -0
- package/lib/openapi/utils/utils/JsonPointer.d.ts +51 -0
- package/lib/openapi/utils/utils/JsonPointer.js +95 -0
- package/lib/openapi/utils/utils/helpers.d.ts +43 -0
- package/lib/openapi/utils/utils/helpers.js +230 -0
- package/lib/openapi/utils/utils/index.d.ts +3 -0
- package/lib/openapi/utils/utils/index.js +25 -0
- package/lib/openapi/utils/utils/openapi.d.ts +40 -0
- package/lib/openapi/utils/utils/openapi.js +605 -0
- package/lib/sidebars/index.js +5 -3
- package/package.json +15 -11
- package/src/markdown/createSchemaDetails.ts +405 -159
- package/src/markdown/index.ts +1 -0
- package/src/markdown/schema.ts +28 -8
- package/src/markdown/utils.ts +5 -2
- package/src/openapi/openapi.ts +42 -38
- package/src/openapi/types.ts +2 -1
- package/src/openapi/utils/loadAndResolveSpec.ts +123 -0
- package/src/openapi/utils/services/OpenAPIParser.ts +433 -0
- package/src/openapi/utils/services/RedocNormalizedOptions.ts +330 -0
- package/src/openapi/utils/types/index.ts +10 -0
- package/src/openapi/utils/types/open-api.ts +303 -0
- package/src/openapi/utils/utils/JsonPointer.ts +99 -0
- package/src/openapi/utils/utils/helpers.ts +239 -0
- package/src/openapi/utils/utils/index.ts +11 -0
- package/src/openapi/utils/utils/openapi.ts +771 -0
- package/src/sidebars/index.ts +7 -4
- package/lib/openapi/utils/loadAndBundleSpec.d.ts +0 -3
- package/src/openapi/utils/loadAndBundleSpec.ts +0 -93
|
@@ -0,0 +1,433 @@
|
|
|
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
|
+
// @ts-nocheck
|
|
9
|
+
|
|
10
|
+
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from "../types";
|
|
11
|
+
import { isArray, isBoolean } from "../utils/helpers";
|
|
12
|
+
import { JsonPointer } from "../utils/JsonPointer";
|
|
13
|
+
import { getDefinitionName, isNamedDefinition } from "../utils/openapi";
|
|
14
|
+
import { RedocNormalizedOptions } from "./RedocNormalizedOptions";
|
|
15
|
+
|
|
16
|
+
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper class to keep track of visited references to avoid
|
|
20
|
+
* endless recursion because of circular refs
|
|
21
|
+
*/
|
|
22
|
+
class RefCounter {
|
|
23
|
+
_counter = {};
|
|
24
|
+
|
|
25
|
+
reset(): void {
|
|
26
|
+
this._counter = {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
visit(ref: string): void {
|
|
30
|
+
this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
exit(ref: string): void {
|
|
34
|
+
this._counter[ref] = this._counter[ref] && this._counter[ref] - 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
visited(ref: string): boolean {
|
|
38
|
+
return !!this._counter[ref];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Loads and keeps spec. Provides raw spec operations
|
|
44
|
+
*/
|
|
45
|
+
export class OpenAPIParser {
|
|
46
|
+
specUrl?: string;
|
|
47
|
+
spec: OpenAPISpec;
|
|
48
|
+
|
|
49
|
+
private _refCounter: RefCounter = new RefCounter();
|
|
50
|
+
private allowMergeRefs: boolean = false;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
spec: OpenAPISpec,
|
|
54
|
+
specUrl?: string,
|
|
55
|
+
private options: {} = new RedocNormalizedOptions()
|
|
56
|
+
) {
|
|
57
|
+
this.validate(spec);
|
|
58
|
+
|
|
59
|
+
this.spec = spec;
|
|
60
|
+
this.allowMergeRefs = spec.openapi.startsWith("3.1");
|
|
61
|
+
|
|
62
|
+
const href = undefined;
|
|
63
|
+
if (typeof specUrl === "string") {
|
|
64
|
+
this.specUrl = new URL(specUrl, href).href;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
validate(spec: any) {
|
|
69
|
+
if (spec.openapi === undefined) {
|
|
70
|
+
throw new Error("Document must be valid OpenAPI 3.0.0 definition");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* get spec part by JsonPointer ($ref)
|
|
76
|
+
*/
|
|
77
|
+
byRef = <T extends any = any>(ref: string): T | undefined => {
|
|
78
|
+
let res;
|
|
79
|
+
if (!this.spec) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (ref.charAt(0) !== "#") {
|
|
83
|
+
ref = "#" + ref;
|
|
84
|
+
}
|
|
85
|
+
ref = decodeURIComponent(ref);
|
|
86
|
+
try {
|
|
87
|
+
res = JsonPointer.get(this.spec, ref);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// do nothing
|
|
90
|
+
}
|
|
91
|
+
return res || {};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* checks if the object is OpenAPI reference (contains $ref property)
|
|
96
|
+
*/
|
|
97
|
+
isRef(obj: any): obj is OpenAPIRef {
|
|
98
|
+
if (!obj) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return obj.$ref !== undefined && obj.$ref !== null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* resets visited endpoints. should be run after
|
|
106
|
+
*/
|
|
107
|
+
resetVisited() {
|
|
108
|
+
if (process.env.NODE_ENV !== "production") {
|
|
109
|
+
// check in dev mode
|
|
110
|
+
for (const k in this._refCounter._counter) {
|
|
111
|
+
if (this._refCounter._counter[k] > 0) {
|
|
112
|
+
console.warn("Not exited reference: " + k);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this._refCounter = new RefCounter();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
exitRef<T>(ref: Referenced<T>) {
|
|
120
|
+
if (!this.isRef(ref)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this._refCounter.exit(ref.$ref);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Resolve given reference object or return as is if it is not a reference
|
|
128
|
+
* @param obj object to dereference
|
|
129
|
+
* @param forceCircular whether to dereference even if it is circular ref
|
|
130
|
+
*/
|
|
131
|
+
deref<T extends object>(
|
|
132
|
+
obj: OpenAPIRef | T,
|
|
133
|
+
forceCircular = false,
|
|
134
|
+
mergeAsAllOf = false
|
|
135
|
+
): T {
|
|
136
|
+
if (this.isRef(obj)) {
|
|
137
|
+
const schemaName = getDefinitionName(obj.$ref);
|
|
138
|
+
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
|
139
|
+
return { type: "object", title: schemaName } as T;
|
|
140
|
+
}
|
|
141
|
+
const resolved = this.byRef<T>(obj.$ref)!;
|
|
142
|
+
const visited = this._refCounter.visited(obj.$ref);
|
|
143
|
+
this._refCounter.visit(obj.$ref);
|
|
144
|
+
if (visited && !forceCircular) {
|
|
145
|
+
// circular reference detected
|
|
146
|
+
// tslint:disable-next-line
|
|
147
|
+
return Object.assign({}, resolved, { "x-circular-ref": true });
|
|
148
|
+
}
|
|
149
|
+
// deref again in case one more $ref is here
|
|
150
|
+
let result = resolved;
|
|
151
|
+
if (this.isRef(resolved)) {
|
|
152
|
+
result = this.deref(resolved, false, mergeAsAllOf);
|
|
153
|
+
this.exitRef(resolved);
|
|
154
|
+
}
|
|
155
|
+
return this.allowMergeRefs
|
|
156
|
+
? this.mergeRefs(obj, resolved, mergeAsAllOf)
|
|
157
|
+
: result;
|
|
158
|
+
}
|
|
159
|
+
return obj;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T {
|
|
163
|
+
if (this.isRef(obj)) {
|
|
164
|
+
const schemaName = getDefinitionName(obj.$ref);
|
|
165
|
+
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
|
166
|
+
return { type: "object", title: schemaName } as T;
|
|
167
|
+
}
|
|
168
|
+
const resolved = this.byRef<T>(obj.$ref);
|
|
169
|
+
return this.allowMergeRefs
|
|
170
|
+
? this.mergeRefs(obj, resolved, false)
|
|
171
|
+
: (resolved as T);
|
|
172
|
+
}
|
|
173
|
+
return obj;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
178
|
+
const { $ref, ...rest } = ref;
|
|
179
|
+
const keys = Object.keys(rest);
|
|
180
|
+
if (keys.length === 0) {
|
|
181
|
+
if (this.isRef(resolved)) {
|
|
182
|
+
return this.shallowDeref(resolved);
|
|
183
|
+
}
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
186
|
+
if (
|
|
187
|
+
mergeAsAllOf &&
|
|
188
|
+
keys.some(
|
|
189
|
+
(k) => k !== "description" && k !== "title" && k !== "externalDocs"
|
|
190
|
+
)
|
|
191
|
+
) {
|
|
192
|
+
return {
|
|
193
|
+
allOf: [rest, resolved],
|
|
194
|
+
};
|
|
195
|
+
} else {
|
|
196
|
+
// small optimization
|
|
197
|
+
return {
|
|
198
|
+
...resolved,
|
|
199
|
+
...rest,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Merge allOf constraints.
|
|
206
|
+
* @param schema schema with allOF
|
|
207
|
+
* @param $ref pointer of the schema
|
|
208
|
+
* @param forceCircular whether to dereference children even if it is a circular ref
|
|
209
|
+
*/
|
|
210
|
+
mergeAllOf(
|
|
211
|
+
schema: OpenAPISchema,
|
|
212
|
+
$ref?: string,
|
|
213
|
+
forceCircular: boolean = false,
|
|
214
|
+
used$Refs = new Set<string>()
|
|
215
|
+
): MergedOpenAPISchema {
|
|
216
|
+
if ($ref) {
|
|
217
|
+
used$Refs.add($ref);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
schema = this.hoistOneOfs(schema);
|
|
221
|
+
|
|
222
|
+
if (schema.allOf === undefined) {
|
|
223
|
+
return schema;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let receiver: MergedOpenAPISchema = {
|
|
227
|
+
...schema,
|
|
228
|
+
allOf: undefined,
|
|
229
|
+
parentRefs: [],
|
|
230
|
+
title: schema.title || getDefinitionName($ref),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// avoid mutating inner objects
|
|
234
|
+
if (
|
|
235
|
+
receiver.properties !== undefined &&
|
|
236
|
+
typeof receiver.properties === "object"
|
|
237
|
+
) {
|
|
238
|
+
receiver.properties = { ...receiver.properties };
|
|
239
|
+
}
|
|
240
|
+
if (receiver.items !== undefined && typeof receiver.items === "object") {
|
|
241
|
+
receiver.items = { ...receiver.items };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const allOfSchemas = schema.allOf
|
|
245
|
+
.map((subSchema) => {
|
|
246
|
+
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const resolved = this.deref(subSchema, forceCircular, true);
|
|
251
|
+
const subRef = subSchema.$ref || undefined;
|
|
252
|
+
const subMerged = this.mergeAllOf(
|
|
253
|
+
resolved,
|
|
254
|
+
subRef,
|
|
255
|
+
forceCircular,
|
|
256
|
+
used$Refs
|
|
257
|
+
);
|
|
258
|
+
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
|
259
|
+
return {
|
|
260
|
+
$ref: subRef,
|
|
261
|
+
schema: subMerged,
|
|
262
|
+
};
|
|
263
|
+
})
|
|
264
|
+
.filter((child) => child !== undefined) as Array<{
|
|
265
|
+
$ref: string | undefined;
|
|
266
|
+
schema: MergedOpenAPISchema;
|
|
267
|
+
}>;
|
|
268
|
+
|
|
269
|
+
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
|
270
|
+
const {
|
|
271
|
+
type,
|
|
272
|
+
enum: enumProperty,
|
|
273
|
+
properties,
|
|
274
|
+
items,
|
|
275
|
+
required,
|
|
276
|
+
oneOf,
|
|
277
|
+
anyOf,
|
|
278
|
+
title,
|
|
279
|
+
...otherConstraints
|
|
280
|
+
} = subSchema;
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
receiver.type !== type &&
|
|
284
|
+
receiver.type !== undefined &&
|
|
285
|
+
type !== undefined
|
|
286
|
+
) {
|
|
287
|
+
console.warn(
|
|
288
|
+
`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${type}"`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (type !== undefined) {
|
|
293
|
+
if (Array.isArray(type) && Array.isArray(receiver.type)) {
|
|
294
|
+
receiver.type = [...type, ...receiver.type];
|
|
295
|
+
} else {
|
|
296
|
+
receiver.type = type;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (enumProperty !== undefined) {
|
|
301
|
+
if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) {
|
|
302
|
+
receiver.enum = [...enumProperty, ...receiver.enum];
|
|
303
|
+
} else {
|
|
304
|
+
receiver.enum = enumProperty;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (properties !== undefined) {
|
|
309
|
+
receiver.properties = receiver.properties || {};
|
|
310
|
+
for (const prop in properties) {
|
|
311
|
+
if (!receiver.properties[prop]) {
|
|
312
|
+
receiver.properties[prop] = properties[prop];
|
|
313
|
+
} else {
|
|
314
|
+
// merge inner properties
|
|
315
|
+
const mergedProp = this.mergeAllOf(
|
|
316
|
+
{ allOf: [receiver.properties[prop], properties[prop]] },
|
|
317
|
+
$ref + "/properties/" + prop
|
|
318
|
+
);
|
|
319
|
+
receiver.properties[prop] = mergedProp;
|
|
320
|
+
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (items !== undefined) {
|
|
326
|
+
const receiverItems = isBoolean(receiver.items)
|
|
327
|
+
? { items: receiver.items }
|
|
328
|
+
: receiver.items
|
|
329
|
+
? (Object.assign({}, receiver.items) as OpenAPISchema)
|
|
330
|
+
: {};
|
|
331
|
+
const subSchemaItems = isBoolean(items)
|
|
332
|
+
? { items }
|
|
333
|
+
: (Object.assign({}, items) as OpenAPISchema);
|
|
334
|
+
// merge inner properties
|
|
335
|
+
receiver.items = this.mergeAllOf(
|
|
336
|
+
{ allOf: [receiverItems, subSchemaItems] },
|
|
337
|
+
$ref + "/items"
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (required !== undefined) {
|
|
342
|
+
receiver.required = (receiver.required || []).concat(required);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (oneOf !== undefined) {
|
|
346
|
+
receiver.oneOf = oneOf;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (anyOf !== undefined) {
|
|
350
|
+
receiver.anyOf = anyOf;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// merge rest of constraints
|
|
354
|
+
// TODO: do more intelligent merge
|
|
355
|
+
receiver = {
|
|
356
|
+
...receiver,
|
|
357
|
+
title: receiver.title || title,
|
|
358
|
+
...otherConstraints,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
if (subSchemaRef) {
|
|
362
|
+
receiver.parentRefs!.push(subSchemaRef);
|
|
363
|
+
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
|
364
|
+
// this is not so correct behaviour. commented out for now
|
|
365
|
+
// ref: https://github.com/Redocly/redoc/issues/601
|
|
366
|
+
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return receiver;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Find all derived definitions among #/components/schemas from any of $refs
|
|
376
|
+
* returns map of definition pointer to definition name
|
|
377
|
+
* @param $refs array of references to find derived from
|
|
378
|
+
*/
|
|
379
|
+
findDerived($refs: string[]): Record<string, string[] | string> {
|
|
380
|
+
const res: Record<string, string[]> = {};
|
|
381
|
+
const schemas =
|
|
382
|
+
(this.spec.components && this.spec.components.schemas) || {};
|
|
383
|
+
for (const defName in schemas) {
|
|
384
|
+
const def = this.deref(schemas[defName]);
|
|
385
|
+
if (
|
|
386
|
+
def.allOf !== undefined &&
|
|
387
|
+
def.allOf.find(
|
|
388
|
+
(obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1
|
|
389
|
+
)
|
|
390
|
+
) {
|
|
391
|
+
res["#/components/schemas/" + defName] = [
|
|
392
|
+
def["x-discriminator-value"] || defName,
|
|
393
|
+
];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return res;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
exitParents(shema: MergedOpenAPISchema) {
|
|
400
|
+
for (const parent$ref of shema.parentRefs || []) {
|
|
401
|
+
this.exitRef({ $ref: parent$ref });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private hoistOneOfs(schema: OpenAPISchema) {
|
|
406
|
+
if (schema.allOf === undefined) {
|
|
407
|
+
return schema;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const allOf = schema.allOf;
|
|
411
|
+
for (let i = 0; i < allOf.length; i++) {
|
|
412
|
+
const sub = allOf[i];
|
|
413
|
+
if (isArray(sub.oneOf)) {
|
|
414
|
+
const beforeAllOf = allOf.slice(0, i);
|
|
415
|
+
const afterAllOf = allOf.slice(i + 1);
|
|
416
|
+
return {
|
|
417
|
+
oneOf: sub.oneOf.map((part) => {
|
|
418
|
+
const merged = this.mergeAllOf({
|
|
419
|
+
allOf: [...beforeAllOf, part, ...afterAllOf],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// each oneOf should be independent so exiting all the parent refs
|
|
423
|
+
// otherwise it will cause false-positive recursive detection
|
|
424
|
+
this.exitParents(merged);
|
|
425
|
+
return merged;
|
|
426
|
+
}),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return schema;
|
|
432
|
+
}
|
|
433
|
+
}
|