docusaurus-plugin-openapi-docs 1.0.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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/lib/index.d.ts +3 -0
  4. package/lib/index.js +194 -0
  5. package/lib/markdown/createDeprecationNotice.d.ts +6 -0
  6. package/lib/markdown/createDeprecationNotice.js +19 -0
  7. package/lib/markdown/createDescription.d.ts +1 -0
  8. package/lib/markdown/createDescription.js +16 -0
  9. package/lib/markdown/createDetails.d.ts +2 -0
  10. package/lib/markdown/createDetails.js +18 -0
  11. package/lib/markdown/createDetailsSummary.d.ts +2 -0
  12. package/lib/markdown/createDetailsSummary.js +18 -0
  13. package/lib/markdown/createFullWidthTable.d.ts +2 -0
  14. package/lib/markdown/createFullWidthTable.js +18 -0
  15. package/lib/markdown/createParamsDetails.d.ts +7 -0
  16. package/lib/markdown/createParamsDetails.js +44 -0
  17. package/lib/markdown/createParamsTable.d.ts +7 -0
  18. package/lib/markdown/createParamsTable.js +80 -0
  19. package/lib/markdown/createRequestBodyDetails.d.ts +6 -0
  20. package/lib/markdown/createRequestBodyDetails.js +14 -0
  21. package/lib/markdown/createRequestBodyTable.d.ts +6 -0
  22. package/lib/markdown/createRequestBodyTable.js +14 -0
  23. package/lib/markdown/createSchemaDetails.d.ts +14 -0
  24. package/lib/markdown/createSchemaDetails.js +241 -0
  25. package/lib/markdown/createSchemaTable.d.ts +14 -0
  26. package/lib/markdown/createSchemaTable.js +217 -0
  27. package/lib/markdown/createStatusCodes.d.ts +6 -0
  28. package/lib/markdown/createStatusCodes.js +47 -0
  29. package/lib/markdown/createVersionBadge.d.ts +1 -0
  30. package/lib/markdown/createVersionBadge.js +20 -0
  31. package/lib/markdown/index.d.ts +3 -0
  32. package/lib/markdown/index.js +43 -0
  33. package/lib/markdown/schema.d.ts +3 -0
  34. package/lib/markdown/schema.js +100 -0
  35. package/lib/markdown/schema.test.d.ts +1 -0
  36. package/lib/markdown/schema.test.js +171 -0
  37. package/lib/markdown/utils.d.ts +7 -0
  38. package/lib/markdown/utils.js +33 -0
  39. package/lib/openapi/createExample.d.ts +2 -0
  40. package/lib/openapi/createExample.js +113 -0
  41. package/lib/openapi/index.d.ts +1 -0
  42. package/lib/openapi/index.js +12 -0
  43. package/lib/openapi/openapi.d.ts +11 -0
  44. package/lib/openapi/openapi.js +233 -0
  45. package/lib/openapi/openapi.test.d.ts +1 -0
  46. package/lib/openapi/openapi.test.js +33 -0
  47. package/lib/openapi/types.d.ts +331 -0
  48. package/lib/openapi/types.js +8 -0
  49. package/lib/options.d.ts +4 -0
  50. package/lib/options.js +18 -0
  51. package/lib/sidebars/index.d.ts +3 -0
  52. package/lib/sidebars/index.js +89 -0
  53. package/lib/types.d.ts +68 -0
  54. package/lib/types.js +8 -0
  55. package/package.json +58 -0
  56. package/src/index.ts +244 -0
  57. package/src/markdown/createDeprecationNotice.ts +30 -0
  58. package/src/markdown/createDescription.ts +13 -0
  59. package/src/markdown/createDetails.ts +16 -0
  60. package/src/markdown/createDetailsSummary.ts +16 -0
  61. package/src/markdown/createFullWidthTable.ts +16 -0
  62. package/src/markdown/createParamsDetails.ts +53 -0
  63. package/src/markdown/createParamsTable.ts +102 -0
  64. package/src/markdown/createRequestBodyDetails.ts +17 -0
  65. package/src/markdown/createRequestBodyTable.ts +17 -0
  66. package/src/markdown/createSchemaDetails.ts +302 -0
  67. package/src/markdown/createSchemaTable.ts +275 -0
  68. package/src/markdown/createStatusCodes.ts +52 -0
  69. package/src/markdown/createVersionBadge.ts +18 -0
  70. package/src/markdown/index.ts +55 -0
  71. package/src/markdown/schema.test.ts +196 -0
  72. package/src/markdown/schema.ts +115 -0
  73. package/src/markdown/utils.ts +39 -0
  74. package/src/openapi/__fixtures__/examples/openapi.yaml +13 -0
  75. package/src/openapi/__fixtures__/examples/yogurtstore/_category_.json +4 -0
  76. package/src/openapi/__fixtures__/examples/yogurtstore/froyo.yaml +13 -0
  77. package/src/openapi/__fixtures__/examples/yogurtstore/nested/nested.yaml +13 -0
  78. package/src/openapi/createExample.ts +143 -0
  79. package/src/openapi/index.ts +8 -0
  80. package/src/openapi/openapi.test.ts +37 -0
  81. package/src/openapi/openapi.ts +293 -0
  82. package/src/openapi/types.ts +430 -0
  83. package/src/openapi-to-postmanv2.d.ts +10 -0
  84. package/src/options.ts +20 -0
  85. package/src/plugin-content-docs-types.d.ts +42 -0
  86. package/src/plugin-openapi.d.ts +87 -0
  87. package/src/sidebars/index.ts +121 -0
  88. package/src/types.ts +97 -0
  89. package/tsconfig.json +7 -0
@@ -0,0 +1,37 @@
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 path from "path";
9
+
10
+ import { readOpenapiFiles } from ".";
11
+
12
+ // npx jest packages/docusaurus-plugin-openapi/src/openapi/openapi.test.ts --watch
13
+
14
+ describe("openapi", () => {
15
+ describe("readOpenapiFiles", () => {
16
+ it("readOpenapiFiles", async () => {
17
+ const results = await readOpenapiFiles(
18
+ path.join(__dirname, "__fixtures__/examples"),
19
+ {}
20
+ );
21
+ const categoryMeta = results.find((x) =>
22
+ x.source.endsWith("_category_.json")
23
+ );
24
+ expect(categoryMeta).toBeFalsy();
25
+ // console.log(results);
26
+ const yaml = results.find((x) => x.source.endsWith("openapi.yaml"));
27
+ expect(yaml).toBeTruthy();
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
+ });
36
+ });
37
+ });
@@ -0,0 +1,293 @@
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 path from "path";
9
+
10
+ import { Globby, GlobExcludeDefault } from "@docusaurus/utils";
11
+ import Converter from "@paloaltonetworks/openapi-to-postmanv2";
12
+ // @ts-ignore
13
+ import sdk, { Collection } from "@paloaltonetworks/postman-collection";
14
+ import chalk from "chalk";
15
+ import fs from "fs-extra";
16
+ import yaml from "js-yaml";
17
+ import JsonRefs from "json-refs";
18
+ import { kebabCase } from "lodash";
19
+
20
+ import { ApiMetadata, ApiPageMetadata, InfoPageMetadata } from "../types";
21
+ import { sampleFromSchema } from "./createExample";
22
+ import { OpenApiObject, OpenApiObjectWithRef, TagObject } from "./types";
23
+
24
+ /**
25
+ * Finds any reference objects in the OpenAPI definition and resolves them to a finalized value.
26
+ */
27
+ async function resolveRefs(openapiData: OpenApiObjectWithRef) {
28
+ const { resolved } = await JsonRefs.resolveRefs(openapiData);
29
+ return resolved as OpenApiObject;
30
+ }
31
+
32
+ /**
33
+ * Convenience function for converting raw JSON to a Postman Collection object.
34
+ */
35
+ function jsonToCollection(data: OpenApiObject): Promise<Collection> {
36
+ return new Promise((resolve, reject) => {
37
+ let schemaPack = new Converter.SchemaPack(
38
+ { type: "json", data },
39
+ { schemaFaker: false }
40
+ );
41
+ schemaPack.computedOptions.schemaFaker = false;
42
+ schemaPack.convert((_err: any, conversionResult: any) => {
43
+ if (!conversionResult.result) {
44
+ return reject(conversionResult.reason);
45
+ }
46
+ return resolve(new sdk.Collection(conversionResult.output[0].data));
47
+ });
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Creates a Postman Collection object from an OpenAPI definition.
53
+ */
54
+ async function createPostmanCollection(
55
+ openapiData: OpenApiObject
56
+ ): Promise<Collection> {
57
+ const data = JSON.parse(JSON.stringify(openapiData)) as OpenApiObject;
58
+
59
+ // Including `servers` breaks postman, so delete all of them.
60
+ delete data.servers;
61
+ for (let pathItemObject of Object.values(data.paths)) {
62
+ delete pathItemObject.servers;
63
+ delete pathItemObject.get?.servers;
64
+ delete pathItemObject.put?.servers;
65
+ delete pathItemObject.post?.servers;
66
+ delete pathItemObject.delete?.servers;
67
+ delete pathItemObject.options?.servers;
68
+ delete pathItemObject.head?.servers;
69
+ delete pathItemObject.patch?.servers;
70
+ delete pathItemObject.trace?.servers;
71
+ }
72
+
73
+ return await jsonToCollection(data);
74
+ }
75
+
76
+ type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
77
+
78
+ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
79
+ // TODO: Find a better way to handle this
80
+ let items: PartialPage<ApiMetadata>[] = [];
81
+
82
+ // Only create an info page if we have a description.
83
+ if (openapiData.info.description) {
84
+ const infoPage: PartialPage<InfoPageMetadata> = {
85
+ type: "info",
86
+ id: "introduction",
87
+ unversionedId: "introduction",
88
+ title: "Introduction",
89
+ description: openapiData.info.description,
90
+ slug: "/introduction",
91
+ frontMatter: {},
92
+ info: {
93
+ ...openapiData.info,
94
+ title: openapiData.info.title ?? "Introduction",
95
+ },
96
+ };
97
+ items.push(infoPage);
98
+ }
99
+
100
+ for (let [path, pathObject] of Object.entries(openapiData.paths)) {
101
+ const { $ref, description, parameters, servers, summary, ...rest } =
102
+ pathObject;
103
+ for (let [method, operationObject] of Object.entries({ ...rest })) {
104
+ const title =
105
+ operationObject.summary ??
106
+ operationObject.operationId ??
107
+ "Missing summary";
108
+ if (operationObject.description === undefined) {
109
+ operationObject.description =
110
+ operationObject.summary ?? operationObject.operationId ?? "";
111
+ }
112
+
113
+ const baseId = kebabCase(title);
114
+
115
+ const servers =
116
+ operationObject.servers ?? pathObject.servers ?? openapiData.servers;
117
+
118
+ const security = operationObject.security ?? openapiData.security;
119
+
120
+ // Add security schemes so we know how to handle security.
121
+ const securitySchemes = openapiData.components?.securitySchemes;
122
+
123
+ // Make sure schemes are lowercase. See: https://github.com/cloud-annotations/docusaurus-plugin-openapi/issues/79
124
+ if (securitySchemes) {
125
+ for (let securityScheme of Object.values(securitySchemes)) {
126
+ if (securityScheme.type === "http") {
127
+ securityScheme.scheme = securityScheme.scheme.toLowerCase();
128
+ }
129
+ }
130
+ }
131
+
132
+ let jsonRequestBodyExample;
133
+ const body = operationObject.requestBody?.content?.["application/json"];
134
+ if (body?.schema) {
135
+ jsonRequestBodyExample = sampleFromSchema(body.schema);
136
+ }
137
+
138
+ // TODO: Don't include summary temporarilly
139
+ const { summary, ...defaults } = operationObject;
140
+
141
+ const apiPage: PartialPage<ApiPageMetadata> = {
142
+ type: "api",
143
+ id: baseId,
144
+ unversionedId: baseId,
145
+ title: title,
146
+ description: description ?? "",
147
+ slug: "/" + baseId,
148
+ frontMatter: {},
149
+ api: {
150
+ ...defaults,
151
+ tags: operationObject.tags?.map((tagName) =>
152
+ getTagDisplayName(tagName, openapiData.tags ?? [])
153
+ ),
154
+ method,
155
+ path,
156
+ servers,
157
+ security,
158
+ securitySchemes,
159
+ jsonRequestBodyExample,
160
+ info: openapiData.info,
161
+ },
162
+ };
163
+
164
+ items.push(apiPage);
165
+ }
166
+ }
167
+
168
+ return items as ApiMetadata[];
169
+ }
170
+
171
+ /**
172
+ * Attach Postman Request objects to the corresponding ApiItems.
173
+ */
174
+ function bindCollectionToApiItems(
175
+ items: ApiMetadata[],
176
+ postmanCollection: sdk.Collection
177
+ ) {
178
+ // @ts-ignore
179
+ postmanCollection.forEachItem((item) => {
180
+ const method = item.request.method.toLowerCase();
181
+ const path = item.request.url
182
+ .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
183
+ .replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
184
+
185
+ const apiItem = items.find((item) => {
186
+ if (item.type === "info") {
187
+ return false;
188
+ }
189
+ return item.api.path === path && item.api.method === method;
190
+ });
191
+
192
+ if (apiItem?.type === "api") {
193
+ apiItem.api.postman = item.request;
194
+ }
195
+ });
196
+ }
197
+
198
+ interface OpenApiFiles {
199
+ source: string;
200
+ sourceDirName: string;
201
+ data: OpenApiObjectWithRef;
202
+ }
203
+
204
+ export async function readOpenapiFiles(
205
+ openapiPath: string,
206
+ _options: {}
207
+ ): Promise<OpenApiFiles[]> {
208
+ const stat = await fs.lstat(openapiPath);
209
+ if (stat.isDirectory()) {
210
+ console.warn(
211
+ chalk.yellow(
212
+ "WARNING: Loading a directory of OpenAPI definitions is experimental and subject to unannounced breaking changes."
213
+ )
214
+ );
215
+
216
+ // TODO: Add config for inlcude/ignore
217
+ const allFiles = await Globby(["**/*.{json,yaml,yml}"], {
218
+ cwd: openapiPath,
219
+ ignore: GlobExcludeDefault,
220
+ });
221
+ const sources = allFiles.filter((x) => !x.includes("_category_")); // todo: regex exclude?
222
+ return Promise.all(
223
+ sources.map(async (source) => {
224
+ // TODO: make a function for this
225
+ const fullPath = path.join(openapiPath, source);
226
+ const openapiString = await fs.readFile(fullPath, "utf-8");
227
+ const data = yaml.load(openapiString) as OpenApiObjectWithRef;
228
+ return {
229
+ source: fullPath, // This will be aliased in process.
230
+ sourceDirName: path.dirname(source),
231
+ data,
232
+ };
233
+ })
234
+ );
235
+ }
236
+ const openapiString = await fs.readFile(openapiPath, "utf-8");
237
+ const data = yaml.load(openapiString) as OpenApiObjectWithRef;
238
+ return [
239
+ {
240
+ source: openapiPath, // This will be aliased in process.
241
+ sourceDirName: ".",
242
+ data,
243
+ },
244
+ ];
245
+ }
246
+
247
+ export async function processOpenapiFiles(
248
+ files: OpenApiFiles[]
249
+ ): Promise<ApiMetadata[]> {
250
+ const promises = files.map(async (file) => {
251
+ const items = await processOpenapiFile(file.data);
252
+ return items.map((item) => ({
253
+ ...item,
254
+ }));
255
+ });
256
+ const metadata = await Promise.all(promises);
257
+ const items = metadata.flat();
258
+ return items;
259
+ }
260
+
261
+ export async function processOpenapiFile(
262
+ openapiDataWithRefs: OpenApiObjectWithRef
263
+ ): Promise<ApiMetadata[]> {
264
+ const openapiData = await resolveRefs(openapiDataWithRefs);
265
+ const postmanCollection = await createPostmanCollection(openapiData);
266
+ const items = createItems(openapiData);
267
+
268
+ bindCollectionToApiItems(items, postmanCollection);
269
+
270
+ return items;
271
+ }
272
+
273
+ // order for picking items as a display name of tags
274
+ const tagDisplayNameProperties = ["x-displayName", "name"] as const;
275
+
276
+ function getTagDisplayName(tagName: string, tags: TagObject[]): string {
277
+ // find the very own tagObject
278
+ const tagObject = tags.find((tagObject) => tagObject.name === tagName) ?? {
279
+ // if none found, just fake one
280
+ name: tagName,
281
+ };
282
+
283
+ // return the first found and filled value from the property list
284
+ for (const property of tagDisplayNameProperties) {
285
+ const displayName = tagObject[property];
286
+ if (typeof displayName === "string") {
287
+ return displayName;
288
+ }
289
+ }
290
+
291
+ // always default to the tagName
292
+ return tagName;
293
+ }