docusaurus-plugin-openapi-docs 1.0.0 → 1.0.3

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 (58) hide show
  1. package/README.md +8 -7
  2. package/lib/index.d.ts +1 -0
  3. package/lib/index.js +80 -9
  4. package/lib/markdown/createAuthentication.d.ts +2 -0
  5. package/lib/markdown/createAuthentication.js +139 -0
  6. package/lib/markdown/createContactInfo.d.ts +2 -0
  7. package/lib/markdown/createContactInfo.js +49 -0
  8. package/lib/markdown/createLicense.d.ts +2 -0
  9. package/lib/markdown/createLicense.js +33 -0
  10. package/lib/markdown/createSchemaDetails.js +4 -1
  11. package/lib/markdown/createStatusCodes.js +1 -1
  12. package/lib/markdown/createTermsOfService.d.ts +1 -0
  13. package/lib/markdown/createTermsOfService.js +32 -0
  14. package/lib/markdown/index.d.ts +3 -2
  15. package/lib/markdown/index.js +17 -3
  16. package/lib/openapi/createExample.js +59 -49
  17. package/lib/openapi/openapi.d.ts +5 -4
  18. package/lib/openapi/openapi.js +94 -50
  19. package/lib/openapi/openapi.test.js +0 -6
  20. package/lib/openapi/types.d.ts +5 -1
  21. package/lib/openapi/utils/loadAndBundleSpec.d.ts +3 -0
  22. package/lib/openapi/utils/loadAndBundleSpec.js +44 -0
  23. package/lib/openapi/utils/types.d.ts +306 -0
  24. package/lib/{markdown/createRequestBodyTable.js → openapi/utils/types.js} +0 -6
  25. package/lib/sidebars/index.d.ts +2 -1
  26. package/lib/sidebars/index.js +87 -30
  27. package/lib/types.d.ts +14 -3
  28. package/package.json +19 -11
  29. package/src/index.ts +108 -11
  30. package/src/markdown/createAuthentication.ts +160 -0
  31. package/src/markdown/createContactInfo.ts +52 -0
  32. package/src/markdown/createLicense.ts +34 -0
  33. package/src/markdown/createSchemaDetails.ts +6 -2
  34. package/src/markdown/createStatusCodes.ts +1 -1
  35. package/src/markdown/createTermsOfService.ts +30 -0
  36. package/src/markdown/index.ts +23 -3
  37. package/src/openapi/createExample.ts +59 -50
  38. package/src/openapi/openapi.test.ts +0 -6
  39. package/src/openapi/openapi.ts +115 -53
  40. package/src/openapi/types.ts +5 -1
  41. package/src/openapi/utils/loadAndBundleSpec.ts +62 -0
  42. package/src/openapi/utils/types.ts +303 -0
  43. package/src/{markdown/createRequestBodyTable.ts → postman-collection.d.ts} +2 -9
  44. package/src/sidebars/index.ts +108 -31
  45. package/src/types.ts +15 -3
  46. package/lib/markdown/createFullWidthTable.d.ts +0 -2
  47. package/lib/markdown/createFullWidthTable.js +0 -18
  48. package/lib/markdown/createParamsTable.d.ts +0 -7
  49. package/lib/markdown/createParamsTable.js +0 -80
  50. package/lib/markdown/createRequestBodyTable.d.ts +0 -6
  51. package/lib/markdown/createSchemaTable.d.ts +0 -14
  52. package/lib/markdown/createSchemaTable.js +0 -217
  53. package/src/markdown/createFullWidthTable.ts +0 -16
  54. package/src/markdown/createParamsTable.ts +0 -102
  55. package/src/markdown/createSchemaTable.ts +0 -275
  56. package/src/openapi/__fixtures__/examples/yogurtstore/_category_.json +0 -4
  57. package/src/openapi/__fixtures__/examples/yogurtstore/froyo.yaml +0 -13
  58. 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
- let { type, example, allOf, properties, items } = schema;
53
+ try {
54
+ let { type, example, allOf, properties, items } = schema;
52
55
 
53
- if (example !== undefined) {
54
- return example;
55
- }
56
+ if (example !== undefined) {
57
+ return example;
58
+ }
56
59
 
57
- if (allOf) {
58
- // TODO: We are just assuming it will always be an object for now
59
- let obj: SchemaObject = {
60
- type: "object",
61
- properties: {},
62
- required: [], // NOTE: We shouldn't need to worry about required
63
- };
64
- for (let item of allOf) {
65
- if (item.properties) {
66
- obj.properties = {
67
- ...obj.properties,
68
- ...item.properties,
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
- if (!type) {
76
- if (properties) {
77
- type = "object";
78
- } else if (items) {
79
- type = "array";
80
- } else {
81
- return;
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
- if (type === "object") {
86
- let obj: any = {};
87
- for (let [name, prop] of Object.entries(properties ?? {})) {
88
- if (prop.deprecated) {
89
- continue;
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[name] = sampleFromSchema(prop);
96
+ return obj;
92
97
  }
93
- return obj;
94
- }
95
98
 
96
- if (type === "array") {
97
- if (Array.isArray(items?.anyOf)) {
98
- return items?.anyOf.map((item) => sampleFromSchema(item));
99
- }
99
+ if (type === "array") {
100
+ if (Array.isArray(items?.anyOf)) {
101
+ return items?.anyOf.map((item) => sampleFromSchema(item));
102
+ }
100
103
 
101
- if (Array.isArray(items?.oneOf)) {
102
- return items?.oneOf.map((item) => sampleFromSchema(item));
103
- }
104
+ if (Array.isArray(items?.oneOf)) {
105
+ return items?.oneOf.map((item) => sampleFromSchema(item));
106
+ }
104
107
 
105
- return [sampleFromSchema(items)];
106
- }
108
+ return [sampleFromSchema(items)];
109
+ }
107
110
 
108
- if (schema.enum) {
109
- if (schema.default) {
110
- return schema.default;
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
- return primitive(schema);
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
  });
@@ -9,17 +9,24 @@ import path from "path";
9
9
 
10
10
  import { Globby, GlobExcludeDefault } from "@docusaurus/utils";
11
11
  import Converter from "@paloaltonetworks/openapi-to-postmanv2";
12
- // @ts-ignore
13
- import sdk, { Collection } from "@paloaltonetworks/postman-collection";
12
+ import sdk from "@paloaltonetworks/postman-collection";
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 { ApiMetadata, ApiPageMetadata, InfoPageMetadata } from "../types";
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,22 +82,59 @@ async function createPostmanCollection(
75
82
 
76
83
  type PartialPage<T> = Omit<T, "permalink" | "source" | "sourceDirName">;
77
84
 
78
- function createItems(openapiData: OpenApiObject): ApiMetadata[] {
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) {
123
+ // Only create an info page if we have a description.
84
124
  const infoPage: PartialPage<InfoPageMetadata> = {
85
125
  type: "info",
86
- id: "introduction",
87
- unversionedId: "introduction",
88
- title: "Introduction",
126
+ id: infoId,
127
+ unversionedId: infoId,
128
+ title: openapiData.info.title,
89
129
  description: openapiData.info.description,
90
- slug: "/introduction",
130
+ slug: "/" + infoId,
91
131
  frontMatter: {},
132
+ securitySchemes: openapiData.components?.securitySchemes,
92
133
  info: {
93
134
  ...openapiData.info,
135
+ tags: openapiData.tags?.map((tagName) =>
136
+ getTagDisplayName(tagName.name!, openapiData.tags ?? [])
137
+ ),
94
138
  title: openapiData.info.title ?? "Introduction",
95
139
  },
96
140
  };
@@ -141,6 +185,7 @@ function createItems(openapiData: OpenApiObject): ApiMetadata[] {
141
185
  const apiPage: PartialPage<ApiPageMetadata> = {
142
186
  type: "api",
143
187
  id: baseId,
188
+ infoId: infoId ?? "",
144
189
  unversionedId: baseId,
145
190
  title: title,
146
191
  description: description ?? "",
@@ -175,15 +220,14 @@ function bindCollectionToApiItems(
175
220
  items: ApiMetadata[],
176
221
  postmanCollection: sdk.Collection
177
222
  ) {
178
- // @ts-ignore
179
- postmanCollection.forEachItem((item) => {
223
+ postmanCollection.forEachItem((item: any) => {
180
224
  const method = item.request.method.toLowerCase();
181
225
  const path = item.request.url
182
226
  .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
183
227
  .replace(/:([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
184
228
 
185
229
  const apiItem = items.find((item) => {
186
- if (item.type === "info") {
230
+ if (item.type === "info" || item.type === "tag") {
187
231
  return false;
188
232
  }
189
233
  return item.api.path === path && item.api.method === method;
@@ -205,36 +249,39 @@ export async function readOpenapiFiles(
205
249
  openapiPath: string,
206
250
  _options: {}
207
251
  ): 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
- );
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
+ }
235
283
  }
236
- const openapiString = await fs.readFile(openapiPath, "utf-8");
237
- const data = yaml.load(openapiString) as OpenApiObjectWithRef;
284
+ const data = (await loadAndBundleSpec(openapiPath)) as OpenApiObjectWithRef;
238
285
  return [
239
286
  {
240
287
  source: openapiPath, // This will be aliased in process.
@@ -245,35 +292,50 @@ export async function readOpenapiFiles(
245
292
  }
246
293
 
247
294
  export async function processOpenapiFiles(
248
- files: OpenApiFiles[]
249
- ): Promise<ApiMetadata[]> {
295
+ files: OpenApiFiles[],
296
+ sidebarOptions: SidebarOptions
297
+ ): Promise<[ApiMetadata[], TagObject[]]> {
250
298
  const promises = files.map(async (file) => {
251
- const items = await processOpenapiFile(file.data);
252
- return items.map((item) => ({
299
+ const processedFile = await processOpenapiFile(file.data, sidebarOptions);
300
+ const itemsObjectsArray = processedFile[0].map((item) => ({
253
301
  ...item,
254
302
  }));
303
+ const tags = processedFile[1];
304
+ return [itemsObjectsArray, tags];
255
305
  });
256
306
  const metadata = await Promise.all(promises);
257
- const items = metadata.flat();
258
- return items;
307
+ const items = metadata
308
+ .map(function (x) {
309
+ return x[0];
310
+ })
311
+ .flat();
312
+ const tags = metadata.map(function (x) {
313
+ return x[1];
314
+ });
315
+ return [items as ApiMetadata[], tags as TagObject[]];
259
316
  }
260
317
 
261
318
  export async function processOpenapiFile(
262
- openapiDataWithRefs: OpenApiObjectWithRef
263
- ): Promise<ApiMetadata[]> {
319
+ openapiDataWithRefs: OpenApiObjectWithRef,
320
+ sidebarOptions: SidebarOptions
321
+ ): Promise<[ApiMetadata[], TagObject[]]> {
264
322
  const openapiData = await resolveRefs(openapiDataWithRefs);
265
323
  const postmanCollection = await createPostmanCollection(openapiData);
266
- const items = createItems(openapiData);
324
+ const items = createItems(openapiData, sidebarOptions);
267
325
 
268
326
  bindCollectionToApiItems(items, postmanCollection);
269
327
 
270
- return items;
328
+ let tags: TagObject[] = [];
329
+ if (openapiData.tags !== undefined) {
330
+ tags = openapiData.tags;
331
+ }
332
+ return [items, tags];
271
333
  }
272
334
 
273
335
  // order for picking items as a display name of tags
274
336
  const tagDisplayNameProperties = ["x-displayName", "name"] as const;
275
337
 
276
- function getTagDisplayName(tagName: string, tags: TagObject[]): string {
338
+ export function getTagDisplayName(tagName: string, tags: TagObject[]): string {
277
339
  // find the very own tagObject
278
340
  const tagObject = tags.find((tagObject) => tagObject.name === tagName) ?? {
279
341
  // if none found, just fake one
@@ -40,6 +40,7 @@ export interface InfoObject {
40
40
  contact?: ContactObject;
41
41
  license?: LicenseObject;
42
42
  version: string;
43
+ tags?: String[];
43
44
  }
44
45
 
45
46
  export interface ContactObject {
@@ -182,6 +183,7 @@ export interface ParameterObject {
182
183
  examples?: Map<ExampleObject>;
183
184
  //
184
185
  content?: Map<MediaTypeObject>;
186
+ param?: Object;
185
187
  // ignoring stylings: matrix, label, form, simple, spaceDelimited,
186
188
  // pipeDelimited and deepObject
187
189
  }
@@ -293,7 +295,7 @@ export type HeaderObject = Omit<ParameterObject, "name" | "in">;
293
295
  export type HeaderObjectWithRef = Omit<ParameterObjectWithRef, "name" | "in">;
294
296
 
295
297
  export interface TagObject {
296
- name: string;
298
+ name?: string;
297
299
  description?: string;
298
300
  externalDocs?: ExternalDocumentationObject;
299
301
  "x-displayName"?: string;
@@ -399,6 +401,8 @@ export interface HttpSecuritySchemeObject {
399
401
  description?: string;
400
402
  scheme: string;
401
403
  bearerFormat?: string;
404
+ name?: string;
405
+ in?: string;
402
406
  }
403
407
 
404
408
  export interface Oauth2SecuritySchemeObject {
@@ -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
+ }