docusaurus-plugin-openapi-docs 1.0.2 → 1.0.5
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 +106 -17
- package/lib/index.d.ts +7 -1
- package/lib/index.js +228 -18
- package/lib/markdown/createAuthentication.d.ts +2 -0
- package/lib/markdown/createAuthentication.js +139 -0
- package/lib/markdown/createParamsDetails.js +2 -0
- package/lib/markdown/createSchemaDetails.js +2 -0
- package/lib/markdown/index.d.ts +3 -2
- package/lib/markdown/index.js +10 -2
- package/lib/openapi/createExample.js +59 -49
- package/lib/openapi/openapi.d.ts +3 -3
- package/lib/openapi/openapi.js +71 -41
- package/lib/openapi/openapi.test.js +0 -6
- package/lib/openapi/utils/loadAndBundleSpec.d.ts +3 -0
- package/lib/openapi/utils/loadAndBundleSpec.js +44 -0
- package/lib/openapi/utils/types.d.ts +306 -0
- package/lib/openapi/utils/types.js +8 -0
- package/lib/options.d.ts +0 -2
- package/lib/options.js +36 -7
- package/lib/sidebars/index.d.ts +1 -1
- package/lib/sidebars/index.js +33 -30
- package/lib/sidebars/utils.d.ts +2 -0
- package/lib/sidebars/utils.js +31 -0
- package/lib/types.d.ts +34 -11
- package/package.json +10 -8
- package/src/index.ts +291 -20
- package/src/markdown/createAuthentication.ts +160 -0
- package/src/markdown/createParamsDetails.ts +2 -0
- package/src/markdown/createSchemaDetails.ts +2 -0
- package/src/markdown/index.ts +15 -2
- package/src/openapi/createExample.ts +59 -50
- package/src/openapi/openapi.test.ts +0 -6
- package/src/openapi/openapi.ts +85 -39
- package/src/openapi/utils/loadAndBundleSpec.ts +62 -0
- package/src/openapi/utils/types.ts +303 -0
- package/src/options.ts +41 -8
- package/src/sidebars/index.ts +40 -30
- package/src/sidebars/utils.ts +29 -0
- package/src/types.ts +35 -9
- package/src/openapi/__fixtures__/examples/yogurtstore/_category_.json +0 -4
- package/src/openapi/__fixtures__/examples/yogurtstore/froyo.yaml +0 -13
- package/src/openapi/__fixtures__/examples/yogurtstore/nested/nested.yaml +0 -13
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
|
|
8
10
|
import { SchemaObject } from "./types";
|
|
9
11
|
|
|
10
12
|
interface OASTypeToTypeMap {
|
|
@@ -48,71 +50,78 @@ const primitives: Primitives = {
|
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export const sampleFromSchema = (schema: SchemaObject = {}): any => {
|
|
51
|
-
|
|
53
|
+
try {
|
|
54
|
+
let { type, example, allOf, properties, items } = schema;
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (example !== undefined) {
|
|
57
|
+
return example;
|
|
58
|
+
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
if (allOf) {
|
|
61
|
+
// TODO: We are just assuming it will always be an object for now
|
|
62
|
+
let obj: SchemaObject = {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {},
|
|
65
|
+
required: [], // NOTE: We shouldn't need to worry about required
|
|
66
|
+
};
|
|
67
|
+
for (let item of allOf) {
|
|
68
|
+
if (item.properties) {
|
|
69
|
+
obj.properties = {
|
|
70
|
+
...obj.properties,
|
|
71
|
+
...item.properties,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
70
74
|
}
|
|
75
|
+
return sampleFromSchema(obj);
|
|
71
76
|
}
|
|
72
|
-
return sampleFromSchema(obj);
|
|
73
|
-
}
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if (!type) {
|
|
79
|
+
if (properties) {
|
|
80
|
+
type = "object";
|
|
81
|
+
} else if (items) {
|
|
82
|
+
type = "array";
|
|
83
|
+
} else {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
82
86
|
}
|
|
83
|
-
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
if (type === "object") {
|
|
89
|
+
let obj: any = {};
|
|
90
|
+
for (let [name, prop] of Object.entries(properties ?? {})) {
|
|
91
|
+
if (prop.deprecated) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
obj[name] = sampleFromSchema(prop);
|
|
90
95
|
}
|
|
91
|
-
obj
|
|
96
|
+
return obj;
|
|
92
97
|
}
|
|
93
|
-
return obj;
|
|
94
|
-
}
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
if (type === "array") {
|
|
100
|
+
if (Array.isArray(items?.anyOf)) {
|
|
101
|
+
return items?.anyOf.map((item) => sampleFromSchema(item));
|
|
102
|
+
}
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
if (Array.isArray(items?.oneOf)) {
|
|
105
|
+
return items?.oneOf.map((item) => sampleFromSchema(item));
|
|
106
|
+
}
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return [sampleFromSchema(items)];
|
|
109
|
+
}
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
if (schema.enum) {
|
|
112
|
+
if (schema.default) {
|
|
113
|
+
return schema.default;
|
|
114
|
+
}
|
|
115
|
+
return normalizeArray(schema.enum)[0];
|
|
111
116
|
}
|
|
112
|
-
return normalizeArray(schema.enum)[0];
|
|
113
|
-
}
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
return primitive(schema);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(
|
|
121
|
+
chalk.yellow("WARNING: failed to create example from schema object:", err)
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
116
125
|
};
|
|
117
126
|
|
|
118
127
|
function primitive(schema: SchemaObject = {}) {
|
|
@@ -26,12 +26,6 @@ describe("openapi", () => {
|
|
|
26
26
|
const yaml = results.find((x) => x.source.endsWith("openapi.yaml"));
|
|
27
27
|
expect(yaml).toBeTruthy();
|
|
28
28
|
expect(yaml?.sourceDirName).toBe(".");
|
|
29
|
-
const froyo = results.find((x) => x.source.endsWith("froyo.yaml"));
|
|
30
|
-
expect(froyo).toBeTruthy();
|
|
31
|
-
expect(froyo?.sourceDirName).toBe("yogurtstore");
|
|
32
|
-
const nested = results.find((x) => x.source.endsWith("nested.yaml"));
|
|
33
|
-
expect(nested).toBeTruthy();
|
|
34
|
-
expect(nested?.sourceDirName).toBe("yogurtstore/nested");
|
|
35
29
|
});
|
|
36
30
|
});
|
|
37
31
|
});
|
package/src/openapi/openapi.ts
CHANGED
|
@@ -13,13 +13,20 @@ import sdk from "@paloaltonetworks/postman-collection";
|
|
|
13
13
|
import Collection from "@paloaltonetworks/postman-collection";
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import fs from "fs-extra";
|
|
16
|
-
import yaml from "js-yaml";
|
|
17
16
|
import JsonRefs from "json-refs";
|
|
18
17
|
import { kebabCase } from "lodash";
|
|
19
18
|
|
|
20
|
-
import {
|
|
19
|
+
import { isURL } from "../index";
|
|
20
|
+
import {
|
|
21
|
+
ApiMetadata,
|
|
22
|
+
ApiPageMetadata,
|
|
23
|
+
InfoPageMetadata,
|
|
24
|
+
SidebarOptions,
|
|
25
|
+
TagPageMetadata,
|
|
26
|
+
} from "../types";
|
|
21
27
|
import { sampleFromSchema } from "./createExample";
|
|
22
28
|
import { OpenApiObject, OpenApiObjectWithRef, TagObject } from "./types";
|
|
29
|
+
import { loadAndBundleSpec } from "./utils/loadAndBundleSpec";
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
32
|
* Finds any reference objects in the OpenAPI definition and resolves them to a finalized value.
|
|
@@ -75,13 +82,45 @@ async function createPostmanCollection(
|
|
|
75
82
|
|
|
76
83
|
type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
|
|
77
84
|
|
|
78
|
-
function createItems(
|
|
85
|
+
function createItems(
|
|
86
|
+
openapiData: OpenApiObject,
|
|
87
|
+
sidebarOptions: SidebarOptions
|
|
88
|
+
): ApiMetadata[] {
|
|
79
89
|
// TODO: Find a better way to handle this
|
|
80
90
|
let items: PartialPage<ApiMetadata>[] = [];
|
|
91
|
+
const infoId = kebabCase(openapiData.info.title);
|
|
92
|
+
|
|
93
|
+
if (sidebarOptions?.categoryLinkSource === "tag") {
|
|
94
|
+
// Only create an tag pages if categoryLinkSource set to tag.
|
|
95
|
+
const tags: TagObject[] = openapiData.tags ?? [];
|
|
96
|
+
// eslint-disable-next-line array-callback-return
|
|
97
|
+
tags
|
|
98
|
+
.filter((tag) => !tag.description?.includes("SchemaDefinition"))
|
|
99
|
+
// eslint-disable-next-line array-callback-return
|
|
100
|
+
.map((tag) => {
|
|
101
|
+
const description = getTagDisplayName(
|
|
102
|
+
tag.name!,
|
|
103
|
+
openapiData.tags ?? []
|
|
104
|
+
);
|
|
105
|
+
const tagId = kebabCase(tag.name);
|
|
106
|
+
const tagPage: PartialPage<TagPageMetadata> = {
|
|
107
|
+
type: "tag",
|
|
108
|
+
id: tagId,
|
|
109
|
+
unversionedId: tagId,
|
|
110
|
+
title: description ?? "",
|
|
111
|
+
description: description ?? "",
|
|
112
|
+
slug: "/" + tagId,
|
|
113
|
+
frontMatter: {},
|
|
114
|
+
tag: {
|
|
115
|
+
...tag,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
items.push(tagPage);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
81
121
|
|
|
82
|
-
// Only create an info page if we have a description.
|
|
83
122
|
if (openapiData.info.description) {
|
|
84
|
-
|
|
123
|
+
// Only create an info page if we have a description.
|
|
85
124
|
const infoPage: PartialPage<InfoPageMetadata> = {
|
|
86
125
|
type: "info",
|
|
87
126
|
id: infoId,
|
|
@@ -90,6 +129,7 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
|
|
|
90
129
|
description: openapiData.info.description,
|
|
91
130
|
slug: "/" + infoId,
|
|
92
131
|
frontMatter: {},
|
|
132
|
+
securitySchemes: openapiData.components?.securitySchemes,
|
|
93
133
|
info: {
|
|
94
134
|
...openapiData.info,
|
|
95
135
|
tags: openapiData.tags?.map((tagName) =>
|
|
@@ -145,6 +185,7 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
|
|
|
145
185
|
const apiPage: PartialPage<ApiPageMetadata> = {
|
|
146
186
|
type: "api",
|
|
147
187
|
id: baseId,
|
|
188
|
+
infoId: infoId ?? "",
|
|
148
189
|
unversionedId: baseId,
|
|
149
190
|
title: title,
|
|
150
191
|
description: description ?? "",
|
|
@@ -186,7 +227,7 @@ function bindCollectionToApiItems(
|
|
|
186
227
|
.replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
|
|
187
228
|
|
|
188
229
|
const apiItem = items.find((item) => {
|
|
189
|
-
if (item.type === "info") {
|
|
230
|
+
if (item.type === "info" || item.type === "tag") {
|
|
190
231
|
return false;
|
|
191
232
|
}
|
|
192
233
|
return item.api.path === path && item.api.method === method;
|
|
@@ -208,36 +249,39 @@ export async function readOpenapiFiles(
|
|
|
208
249
|
openapiPath: string,
|
|
209
250
|
_options: {}
|
|
210
251
|
): Promise<OpenApiFiles[]> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
sources.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
if (!isURL(openapiPath)) {
|
|
253
|
+
const stat = await fs.lstat(openapiPath);
|
|
254
|
+
if (stat.isDirectory()) {
|
|
255
|
+
console.warn(
|
|
256
|
+
chalk.yellow(
|
|
257
|
+
"WARNING: Loading a directory of OpenAPI definitions is experimental and subject to unannounced breaking changes."
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// TODO: Add config for inlcude/ignore
|
|
262
|
+
const allFiles = await Globby(["**/*.{json,yaml,yml}"], {
|
|
263
|
+
cwd: openapiPath,
|
|
264
|
+
ignore: GlobExcludeDefault,
|
|
265
|
+
deep: 1,
|
|
266
|
+
});
|
|
267
|
+
const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?
|
|
268
|
+
return Promise.all(
|
|
269
|
+
sources.map(async (source) => {
|
|
270
|
+
// TODO: make a function for this
|
|
271
|
+
const fullPath = path.join(openapiPath, source);
|
|
272
|
+
const data = (await loadAndBundleSpec(
|
|
273
|
+
fullPath
|
|
274
|
+
)) as OpenApiObjectWithRef;
|
|
275
|
+
return {
|
|
276
|
+
source: fullPath, // This will be aliased in process.
|
|
277
|
+
sourceDirName: path.dirname(source),
|
|
278
|
+
data,
|
|
279
|
+
};
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
}
|
|
238
283
|
}
|
|
239
|
-
const
|
|
240
|
-
const data = yaml.load(openapiString) as OpenApiObjectWithRef;
|
|
284
|
+
const data = (await loadAndBundleSpec(openapiPath)) as OpenApiObjectWithRef;
|
|
241
285
|
return [
|
|
242
286
|
{
|
|
243
287
|
source: openapiPath, // This will be aliased in process.
|
|
@@ -248,10 +292,11 @@ export async function readOpenapiFiles(
|
|
|
248
292
|
}
|
|
249
293
|
|
|
250
294
|
export async function processOpenapiFiles(
|
|
251
|
-
files: OpenApiFiles[]
|
|
295
|
+
files: OpenApiFiles[],
|
|
296
|
+
sidebarOptions: SidebarOptions
|
|
252
297
|
): Promise<[ApiMetadata[], TagObject[]]> {
|
|
253
298
|
const promises = files.map(async (file) => {
|
|
254
|
-
const processedFile = await processOpenapiFile(file.data);
|
|
299
|
+
const processedFile = await processOpenapiFile(file.data, sidebarOptions);
|
|
255
300
|
const itemsObjectsArray = processedFile[0].map((item) => ({
|
|
256
301
|
...item,
|
|
257
302
|
}));
|
|
@@ -271,11 +316,12 @@ export async function processOpenapiFiles(
|
|
|
271
316
|
}
|
|
272
317
|
|
|
273
318
|
export async function processOpenapiFile(
|
|
274
|
-
openapiDataWithRefs: OpenApiObjectWithRef
|
|
319
|
+
openapiDataWithRefs: OpenApiObjectWithRef,
|
|
320
|
+
sidebarOptions: SidebarOptions
|
|
275
321
|
): Promise<[ApiMetadata[], TagObject[]]> {
|
|
276
322
|
const openapiData = await resolveRefs(openapiDataWithRefs);
|
|
277
323
|
const postmanCollection = await createPostmanCollection(openapiData);
|
|
278
|
-
const items = createItems(openapiData);
|
|
324
|
+
const items = createItems(openapiData, sidebarOptions);
|
|
279
325
|
|
|
280
326
|
bindCollectionToApiItems(items, postmanCollection);
|
|
281
327
|
|
|
@@ -0,0 +1,62 @@
|
|
|
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 type { Source, Document } from "@redocly/openapi-core";
|
|
11
|
+
import { bundle } from "@redocly/openapi-core/lib/bundle";
|
|
12
|
+
import type { ResolvedConfig } from "@redocly/openapi-core/lib/config";
|
|
13
|
+
import { Config } from "@redocly/openapi-core/lib/config/config";
|
|
14
|
+
import { convertObj } from "swagger2openapi";
|
|
15
|
+
|
|
16
|
+
import { OpenAPISpec } from "./types";
|
|
17
|
+
|
|
18
|
+
export async function loadAndBundleSpec(
|
|
19
|
+
specUrlOrObject: object | string
|
|
20
|
+
): Promise<OpenAPISpec> {
|
|
21
|
+
const config = new Config({} as ResolvedConfig);
|
|
22
|
+
const bundleOpts = {
|
|
23
|
+
config,
|
|
24
|
+
base: process.cwd(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (typeof specUrlOrObject === "object" && specUrlOrObject !== null) {
|
|
28
|
+
bundleOpts["doc"] = {
|
|
29
|
+
source: { absoluteRef: "" } as Source,
|
|
30
|
+
parsed: specUrlOrObject,
|
|
31
|
+
} as Document;
|
|
32
|
+
} else {
|
|
33
|
+
bundleOpts["ref"] = specUrlOrObject;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Force dereference ?
|
|
37
|
+
// bundleOpts["dereference"] = true;
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
bundle: { parsed },
|
|
41
|
+
} = await bundle(bundleOpts);
|
|
42
|
+
return parsed.swagger !== undefined ? convertSwagger2OpenAPI(parsed) : parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec> {
|
|
46
|
+
console.warn(
|
|
47
|
+
"[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"
|
|
48
|
+
);
|
|
49
|
+
return new Promise<OpenAPISpec>((resolve, reject) =>
|
|
50
|
+
convertObj(
|
|
51
|
+
spec,
|
|
52
|
+
{ patch: true, warnOnly: true, text: "{}", anchors: true },
|
|
53
|
+
(err, res) => {
|
|
54
|
+
// TODO: log any warnings
|
|
55
|
+
if (err) {
|
|
56
|
+
return reject(err);
|
|
57
|
+
}
|
|
58
|
+
resolve(res && (res.openapi as any));
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|