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.
Files changed (47) hide show
  1. package/README.md +1 -2
  2. package/lib/markdown/createSchemaDetails.js +325 -132
  3. package/lib/markdown/index.js +1 -0
  4. package/lib/markdown/schema.js +25 -9
  5. package/lib/markdown/utils.d.ts +1 -1
  6. package/lib/markdown/utils.js +4 -1
  7. package/lib/openapi/openapi.d.ts +3 -3
  8. package/lib/openapi/openapi.js +30 -26
  9. package/lib/openapi/types.d.ts +2 -1
  10. package/lib/openapi/utils/loadAndResolveSpec.d.ts +2 -0
  11. package/lib/openapi/utils/{loadAndBundleSpec.js → loadAndResolveSpec.js} +61 -28
  12. package/lib/openapi/utils/services/OpenAPIParser.d.ts +52 -0
  13. package/lib/openapi/utils/services/OpenAPIParser.js +342 -0
  14. package/lib/openapi/utils/services/RedocNormalizedOptions.d.ts +100 -0
  15. package/lib/openapi/utils/services/RedocNormalizedOptions.js +170 -0
  16. package/lib/openapi/utils/types/index.d.ts +2 -0
  17. package/lib/openapi/utils/types/index.js +23 -0
  18. package/lib/openapi/utils/types/open-api.d.ts +305 -0
  19. package/lib/openapi/utils/types/open-api.js +8 -0
  20. package/lib/openapi/utils/utils/JsonPointer.d.ts +51 -0
  21. package/lib/openapi/utils/utils/JsonPointer.js +95 -0
  22. package/lib/openapi/utils/utils/helpers.d.ts +43 -0
  23. package/lib/openapi/utils/utils/helpers.js +230 -0
  24. package/lib/openapi/utils/utils/index.d.ts +3 -0
  25. package/lib/openapi/utils/utils/index.js +25 -0
  26. package/lib/openapi/utils/utils/openapi.d.ts +40 -0
  27. package/lib/openapi/utils/utils/openapi.js +605 -0
  28. package/lib/sidebars/index.js +5 -3
  29. package/package.json +15 -11
  30. package/src/markdown/createSchemaDetails.ts +405 -159
  31. package/src/markdown/index.ts +1 -0
  32. package/src/markdown/schema.ts +28 -8
  33. package/src/markdown/utils.ts +5 -2
  34. package/src/openapi/openapi.ts +42 -38
  35. package/src/openapi/types.ts +2 -1
  36. package/src/openapi/utils/loadAndResolveSpec.ts +123 -0
  37. package/src/openapi/utils/services/OpenAPIParser.ts +433 -0
  38. package/src/openapi/utils/services/RedocNormalizedOptions.ts +330 -0
  39. package/src/openapi/utils/types/index.ts +10 -0
  40. package/src/openapi/utils/types/open-api.ts +303 -0
  41. package/src/openapi/utils/utils/JsonPointer.ts +99 -0
  42. package/src/openapi/utils/utils/helpers.ts +239 -0
  43. package/src/openapi/utils/utils/index.ts +11 -0
  44. package/src/openapi/utils/utils/openapi.ts +771 -0
  45. package/src/sidebars/index.ts +7 -4
  46. package/lib/openapi/utils/loadAndBundleSpec.d.ts +0 -3
  47. 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
+ }