docusaurus-plugin-openapi-docs 2.0.4 → 2.1.1

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 (49) hide show
  1. package/README.md +2 -0
  2. package/lib/index.js +61 -8
  3. package/lib/markdown/createStatusCodes.js +1 -1
  4. package/lib/markdown/index.d.ts +2 -1
  5. package/lib/markdown/index.js +18 -2
  6. package/lib/markdown/utils.d.ts +2 -2
  7. package/lib/markdown/utils.js +1 -1
  8. package/lib/openapi/openapi.d.ts +3 -3
  9. package/lib/openapi/openapi.js +60 -7
  10. package/lib/openapi/openapi.test.js +2 -0
  11. package/lib/openapi/types.d.ts +18 -13
  12. package/lib/openapi/utils/services/OpenAPIParser.d.ts +1 -1
  13. package/lib/openapi/utils/services/OpenAPIParser.js +2 -1
  14. package/lib/openapi/utils/services/RedocNormalizedOptions.js +49 -49
  15. package/lib/openapi/utils/types/index.d.ts +1 -1
  16. package/lib/openapi/utils/types/open-api.d.ts +4 -4
  17. package/lib/openapi/utils/types.d.ts +5 -5
  18. package/lib/options.js +2 -1
  19. package/lib/sidebars/index.d.ts +2 -2
  20. package/lib/sidebars/index.js +50 -10
  21. package/lib/types.d.ts +16 -3
  22. package/package.json +4 -4
  23. package/src/index.ts +113 -10
  24. package/src/markdown/createAuthentication.ts +1 -1
  25. package/src/markdown/createCallbacks.ts +2 -2
  26. package/src/markdown/createContactInfo.ts +1 -1
  27. package/src/markdown/createLicense.ts +1 -1
  28. package/src/markdown/createLogo.ts +1 -1
  29. package/src/markdown/createParamsDetails.ts +1 -1
  30. package/src/markdown/createRequestBodyDetails.ts +1 -1
  31. package/src/markdown/createRequestSchema.ts +1 -1
  32. package/src/markdown/createResponseSchema.ts +1 -1
  33. package/src/markdown/createSchema.test.ts +1 -1
  34. package/src/markdown/createSchema.ts +1 -1
  35. package/src/markdown/createStatusCodes.ts +2 -2
  36. package/src/markdown/index.ts +30 -9
  37. package/src/markdown/utils.ts +1 -1
  38. package/src/openapi/__fixtures__/examples/openapi.yaml +29 -0
  39. package/src/openapi/createRequestExample.ts +1 -1
  40. package/src/openapi/createResponseExample.ts +1 -1
  41. package/src/openapi/openapi.test.ts +3 -0
  42. package/src/openapi/openapi.ts +77 -9
  43. package/src/openapi/types.ts +6 -0
  44. package/src/openapi/utils/loadAndResolveSpec.ts +1 -1
  45. package/src/openapi/utils/services/OpenAPIParser.ts +1 -1
  46. package/src/openapi/utils/utils/openapi.ts +7 -7
  47. package/src/options.ts +2 -1
  48. package/src/sidebars/index.ts +71 -16
  49. package/src/types.ts +21 -1
@@ -24,7 +24,7 @@ export function guard<T>(
24
24
  value: T | undefined,
25
25
  cb: (value: T) => Children
26
26
  ): string {
27
- if (!!value) {
27
+ if (!!value || value === 0) {
28
28
  const children = cb(value);
29
29
  return render(children);
30
30
  }
@@ -11,3 +11,32 @@ paths:
11
11
  responses:
12
12
  200:
13
13
  description: OK
14
+
15
+ tags:
16
+ - name: tag1
17
+ description: Everything about your Pets
18
+ x-displayName: Tag 1
19
+ - name: tag2
20
+ description: Tag 2 description
21
+ x-displayName: Tag 2
22
+ - name: tag3
23
+ description: Tag 3 description
24
+ x-displayName: Tag 3
25
+ - name: tag4
26
+ description: Tag 4 description
27
+ x-displayName: Tag 4
28
+
29
+ x-tagGroups:
30
+ - name: Tag 1 & 2
31
+ tags:
32
+ - tag1
33
+ - tag2
34
+ - name: Trinity
35
+ tags:
36
+ - tag1
37
+ - tag2
38
+ - tag3
39
+ - name: Last Two
40
+ tags:
41
+ - tag3
42
+ - tag4
@@ -8,8 +8,8 @@
8
8
  import chalk from "chalk";
9
9
  import merge from "lodash/merge";
10
10
 
11
- import { mergeAllOf } from "../markdown/createSchema";
12
11
  import { SchemaObject } from "./types";
12
+ import { mergeAllOf } from "../markdown/createSchema";
13
13
 
14
14
  interface OASTypeToTypeMap {
15
15
  string: string;
@@ -8,8 +8,8 @@
8
8
  import chalk from "chalk";
9
9
  import merge from "lodash/merge";
10
10
 
11
- import { mergeAllOf } from "../markdown/createSchema";
12
11
  import { SchemaObject } from "./types";
12
+ import { mergeAllOf } from "../markdown/createSchema";
13
13
 
14
14
  interface OASTypeToTypeMap {
15
15
  string: string;
@@ -28,6 +28,9 @@ describe("openapi", () => {
28
28
  const yaml = results.find((x) => x.source.endsWith("openapi.yaml"));
29
29
  expect(yaml).toBeTruthy();
30
30
  expect(yaml?.sourceDirName).toBe(".");
31
+
32
+ expect(yaml?.data.tags).toBeDefined();
33
+ expect(yaml?.data["x-tagGroups"]).toBeDefined();
31
34
  });
32
35
  });
33
36
  });
@@ -18,18 +18,19 @@ import kebabCase from "lodash/kebabCase";
18
18
  import unionBy from "lodash/unionBy";
19
19
  import uniq from "lodash/uniq";
20
20
 
21
+ import { sampleRequestFromSchema } from "./createRequestExample";
22
+ import { OpenApiObject, TagGroupObject, TagObject } from "./types";
23
+ import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";
21
24
  import { isURL } from "../index";
22
25
  import {
23
26
  ApiMetadata,
24
27
  APIOptions,
25
28
  ApiPageMetadata,
26
29
  InfoPageMetadata,
30
+ SchemaPageMetadata,
27
31
  SidebarOptions,
28
32
  TagPageMetadata,
29
33
  } from "../types";
30
- import { sampleRequestFromSchema } from "./createRequestExample";
31
- import { OpenApiObject, TagObject } from "./types";
32
- import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";
33
34
 
34
35
  /**
35
36
  * Convenience function for converting raw JSON to a Postman Collection object.
@@ -409,6 +410,46 @@ function createItems(
409
410
  }
410
411
  }
411
412
 
413
+ if (options?.showSchemas === true) {
414
+ // Gather schemas
415
+ for (let [schema, schemaObject] of Object.entries(
416
+ openapiData?.components?.schemas ?? {}
417
+ )) {
418
+ const baseIdSpaces =
419
+ schemaObject?.title?.replace(" ", "-").toLowerCase() ?? "";
420
+ const baseId = kebabCase(baseIdSpaces);
421
+
422
+ const schemaDescription = schemaObject.description;
423
+ let splitDescription: any;
424
+ if (schemaDescription) {
425
+ splitDescription = schemaDescription.match(/[^\r\n]+/g);
426
+ }
427
+
428
+ const schemaPage: PartialPage<SchemaPageMetadata> = {
429
+ type: "schema",
430
+ id: baseId,
431
+ infoId: infoId ?? "",
432
+ unversionedId: baseId,
433
+ title: schemaObject.title
434
+ ? schemaObject.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
435
+ : schema,
436
+ description: schemaObject.description
437
+ ? schemaObject.description.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
438
+ : "",
439
+ frontMatter: {
440
+ description: splitDescription
441
+ ? splitDescription[0]
442
+ .replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
443
+ .replace(/\s+$/, "")
444
+ : "",
445
+ },
446
+ schema: schemaObject,
447
+ };
448
+
449
+ items.push(schemaPage);
450
+ }
451
+ }
452
+
412
453
  if (sidebarOptions?.categoryLinkSource === "tag") {
413
454
  // Get global tags
414
455
  const tags: TagObject[] = openapiData.tags ?? [];
@@ -471,7 +512,11 @@ function bindCollectionToApiItems(
471
512
  .getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
472
513
  .replace(/(?<![a-z0-9-_]+):([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
473
514
  const apiItem = items.find((item) => {
474
- if (item.type === "info" || item.type === "tag") {
515
+ if (
516
+ item.type === "info" ||
517
+ item.type === "tag" ||
518
+ item.type === "schema"
519
+ ) {
475
520
  return false;
476
521
  }
477
522
  return item.api.path === path && item.api.method === method;
@@ -534,7 +579,7 @@ export async function processOpenapiFiles(
534
579
  files: OpenApiFiles[],
535
580
  options: APIOptions,
536
581
  sidebarOptions: SidebarOptions
537
- ): Promise<[ApiMetadata[], TagObject[][]]> {
582
+ ): Promise<[ApiMetadata[], TagObject[][], TagGroupObject[]]> {
538
583
  const promises = files.map(async (file) => {
539
584
  if (file.data !== undefined) {
540
585
  const processedFile = await processOpenapiFile(
@@ -546,7 +591,8 @@ export async function processOpenapiFiles(
546
591
  ...item,
547
592
  }));
548
593
  const tags = processedFile[1];
549
- return [itemsObjectsArray, tags];
594
+ const tagGroups = processedFile[2];
595
+ return [itemsObjectsArray, tags, tagGroups];
550
596
  }
551
597
  console.warn(
552
598
  chalk.yellow(
@@ -565,6 +611,7 @@ export async function processOpenapiFiles(
565
611
  // Remove undefined items due to transient parsing errors
566
612
  return x !== undefined;
567
613
  });
614
+
568
615
  const tags = metadata
569
616
  .map(function (x) {
570
617
  return x[1];
@@ -573,14 +620,29 @@ export async function processOpenapiFiles(
573
620
  // Remove undefined tags due to transient parsing errors
574
621
  return x !== undefined;
575
622
  });
576
- return [items as ApiMetadata[], tags as TagObject[][]];
623
+
624
+ const tagGroups = metadata
625
+ .map(function (x) {
626
+ return x[2];
627
+ })
628
+ .flat()
629
+ .filter(function (x) {
630
+ // Remove undefined tags due to transient parsing errors
631
+ return x !== undefined;
632
+ });
633
+
634
+ return [
635
+ items as ApiMetadata[],
636
+ tags as TagObject[][],
637
+ tagGroups as TagGroupObject[],
638
+ ];
577
639
  }
578
640
 
579
641
  export async function processOpenapiFile(
580
642
  openapiData: OpenApiObject,
581
643
  options: APIOptions,
582
644
  sidebarOptions: SidebarOptions
583
- ): Promise<[ApiMetadata[], TagObject[]]> {
645
+ ): Promise<[ApiMetadata[], TagObject[], TagGroupObject[]]> {
584
646
  const postmanCollection = await createPostmanCollection(openapiData);
585
647
  const items = createItems(openapiData, options, sidebarOptions);
586
648
 
@@ -590,7 +652,13 @@ export async function processOpenapiFile(
590
652
  if (openapiData.tags !== undefined) {
591
653
  tags = openapiData.tags;
592
654
  }
593
- return [items, tags];
655
+
656
+ let tagGroups: TagGroupObject[] = [];
657
+ if (openapiData["x-tagGroups"] !== undefined) {
658
+ tagGroups = openapiData["x-tagGroups"];
659
+ }
660
+
661
+ return [items, tags, tagGroups];
594
662
  }
595
663
 
596
664
  // order for picking items as a display name of tags
@@ -22,6 +22,7 @@ export interface OpenApiObject {
22
22
  externalDocs?: ExternalDocumentationObject;
23
23
  swagger?: string;
24
24
  "x-webhooks"?: PathsObject;
25
+ "x-tagGroups"?: TagGroupObject[];
25
26
  }
26
27
 
27
28
  export interface OpenApiObjectWithRef {
@@ -311,6 +312,11 @@ export interface TagObject {
311
312
  "x-displayName"?: string;
312
313
  }
313
314
 
315
+ export interface TagGroupObject {
316
+ name: string;
317
+ tags: string[];
318
+ }
319
+
314
320
  export interface ReferenceObject {
315
321
  $ref: string;
316
322
  }
@@ -13,8 +13,8 @@ import chalk from "chalk";
13
13
  // @ts-ignore
14
14
  import { convertObj } from "swagger2openapi";
15
15
 
16
- import { OpenApiObject } from "../types";
17
16
  import { OpenAPIParser } from "./services/OpenAPIParser";
17
+ import { OpenApiObject } from "../types";
18
18
 
19
19
  function serializer(replacer: any, cycleReplacer: any) {
20
20
  var stack: any = [],
@@ -7,11 +7,11 @@
7
7
 
8
8
  // @ts-nocheck
9
9
 
10
+ import { RedocNormalizedOptions } from "./RedocNormalizedOptions";
10
11
  import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from "../types";
11
12
  import { isArray, isBoolean } from "../utils/helpers";
12
13
  import { JsonPointer } from "../utils/JsonPointer";
13
14
  import { getDefinitionName, isNamedDefinition } from "../utils/openapi";
14
- import { RedocNormalizedOptions } from "./RedocNormalizedOptions";
15
15
 
16
16
  export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
17
17
 
@@ -9,6 +9,13 @@
9
9
 
10
10
  import { dirname } from "path";
11
11
 
12
+ import {
13
+ isNumeric,
14
+ removeQueryString,
15
+ resolveUrl,
16
+ isArray,
17
+ isBoolean,
18
+ } from "./helpers";
12
19
  import { OpenAPIParser } from "../services/OpenAPIParser";
13
20
  import {
14
21
  OpenAPIEncoding,
@@ -21,13 +28,6 @@ import {
21
28
  OpenAPIServer,
22
29
  Referenced,
23
30
  } from "../types";
24
- import {
25
- isNumeric,
26
- removeQueryString,
27
- resolveUrl,
28
- isArray,
29
- isBoolean,
30
- } from "./helpers";
31
31
 
32
32
  function isWildcardStatusCode(
33
33
  statusCode: string | number
package/src/options.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { Joi } from "@docusaurus/utils-validation";
9
9
 
10
10
  const sidebarOptions = Joi.object({
11
- groupPathsBy: Joi.string().valid("tag"),
11
+ groupPathsBy: Joi.string().valid("tag", "tagGroup"),
12
12
  categoryLinkSource: Joi.string().valid("tag", "info", "auto"),
13
13
  customProps: Joi.object(),
14
14
  sidebarCollapsible: Joi.boolean(),
@@ -37,6 +37,7 @@ export const OptionsSchema = Joi.object({
37
37
  showExtensions: Joi.boolean(),
38
38
  sidebarOptions: sidebarOptions,
39
39
  markdownGenerators: markdownGenerators,
40
+ showSchemas: Joi.boolean(),
40
41
  version: Joi.string().when("versions", {
41
42
  is: Joi.exist(),
42
43
  then: Joi.required(),
@@ -7,6 +7,7 @@
7
7
 
8
8
  import path from "path";
9
9
 
10
+ import { ProcessedSidebarItem } from "@docusaurus/plugin-content-docs/lib/sidebars/types";
10
11
  import {
11
12
  ProcessedSidebar,
12
13
  SidebarItemCategory,
@@ -18,12 +19,13 @@ import clsx from "clsx";
18
19
  import { kebabCase } from "lodash";
19
20
  import uniq from "lodash/uniq";
20
21
 
21
- import { TagObject } from "../openapi/types";
22
+ import { TagGroupObject, TagObject } from "../openapi/types";
22
23
  import type {
23
24
  SidebarOptions,
24
25
  APIOptions,
25
26
  ApiPageMetadata,
26
27
  ApiMetadata,
28
+ SchemaPageMetadata,
27
29
  } from "../types";
28
30
 
29
31
  function isApiItem(item: ApiMetadata): item is ApiMetadata {
@@ -34,6 +36,10 @@ function isInfoItem(item: ApiMetadata): item is ApiMetadata {
34
36
  return item.type === "info";
35
37
  }
36
38
 
39
+ function isSchemaItem(item: ApiMetadata): item is ApiMetadata {
40
+ return item.type === "schema";
41
+ }
42
+
37
43
  function groupByTags(
38
44
  items: ApiPageMetadata[],
39
45
  sidebarOptions: SidebarOptions,
@@ -55,6 +61,7 @@ function groupByTags(
55
61
 
56
62
  const apiItems = items.filter(isApiItem);
57
63
  const infoItems = items.filter(isInfoItem);
64
+ const schemaItems = items.filter(isSchemaItem);
58
65
  const intros = infoItems.map((item: any) => {
59
66
  return {
60
67
  id: item.id,
@@ -80,28 +87,35 @@ function groupByTags(
80
87
  apiTags.push(tag.name!);
81
88
  }
82
89
  });
83
- apiTags = uniq(apiTags.concat(operationTags));
90
+ // apiTags = uniq(apiTags.concat(operationTags));
84
91
 
85
92
  const basePath = docPath
86
93
  ? outputDir.split(docPath!)[1].replace(/^\/+/g, "")
87
94
  : outputDir.slice(outputDir.indexOf("/", 1)).replace(/^\/+/g, "");
88
- function createDocItem(item: ApiPageMetadata): SidebarItemDoc {
95
+ function createDocItem(
96
+ item: ApiPageMetadata | SchemaPageMetadata
97
+ ): SidebarItemDoc {
89
98
  const sidebar_label = item.frontMatter.sidebar_label;
90
99
  const title = item.title;
91
- const id = item.id;
100
+ const id = item.type === "schema" ? `schemas/${item.id}` : item.id;
101
+ const className =
102
+ item.type === "api"
103
+ ? clsx(
104
+ {
105
+ "menu__list-item--deprecated": item.api.deprecated,
106
+ "api-method": !!item.api.method,
107
+ },
108
+ item.api.method
109
+ )
110
+ : clsx({
111
+ "menu__list-item--deprecated": item.schema.deprecated,
112
+ });
92
113
  return {
93
114
  type: "doc" as const,
94
- id:
95
- basePath === "" || undefined ? `${item.id}` : `${basePath}/${item.id}`,
115
+ id: basePath === "" || undefined ? `${id}` : `${basePath}/${id}`,
96
116
  label: (sidebar_label as string) ?? title ?? id,
97
117
  customProps: customProps,
98
- className: clsx(
99
- {
100
- "menu__list-item--deprecated": item.api.deprecated,
101
- "api-method": !!item.api.method,
102
- },
103
- item.api.method
104
- ),
118
+ className: className ? className : undefined,
105
119
  };
106
120
  }
107
121
 
@@ -201,13 +215,26 @@ function groupByTags(
201
215
  ];
202
216
  }
203
217
 
218
+ let schemas: SidebarItemCategory[] = [];
219
+ if (schemaItems.length > 0) {
220
+ schemas = [
221
+ {
222
+ type: "category" as const,
223
+ label: "Schemas",
224
+ collapsible: sidebarCollapsible!,
225
+ collapsed: sidebarCollapsed!,
226
+ items: schemaItems.map(createDocItem),
227
+ },
228
+ ];
229
+ }
230
+
204
231
  // Shift root intro doc to top of sidebar
205
232
  // TODO: Add input validation for categoryLinkSource options
206
233
  if (rootIntroDoc && categoryLinkSource !== "info") {
207
234
  tagged.unshift(rootIntroDoc as any);
208
235
  }
209
236
 
210
- return [...tagged, ...untagged];
237
+ return [...tagged, ...untagged, ...schemas];
211
238
  }
212
239
 
213
240
  export default function generateSidebarSlice(
@@ -215,11 +242,38 @@ export default function generateSidebarSlice(
215
242
  options: APIOptions,
216
243
  api: ApiMetadata[],
217
244
  tags: TagObject[][],
218
- docPath: string
245
+ docPath: string,
246
+ tagGroups?: TagGroupObject[]
219
247
  ) {
220
248
  let sidebarSlice: ProcessedSidebar = [];
221
249
 
222
- if (sidebarOptions.groupPathsBy === "tag") {
250
+ if (sidebarOptions.groupPathsBy === "tagGroup") {
251
+ tagGroups?.forEach((tagGroup) => {
252
+ //filter tags only included in group
253
+ const filteredTags: TagObject[] = [];
254
+ tags[0].forEach((tag) => {
255
+ if (tagGroup.tags.includes(tag.name as string)) {
256
+ filteredTags.push(tag);
257
+ }
258
+ });
259
+
260
+ const groupCategory = {
261
+ type: "category" as const,
262
+ label: tagGroup.name,
263
+ collapsible: true,
264
+ collapsed: true,
265
+ items: groupByTags(
266
+ api as ApiPageMetadata[],
267
+ sidebarOptions,
268
+ options,
269
+ [filteredTags],
270
+ docPath
271
+ ),
272
+ } as ProcessedSidebarItem;
273
+
274
+ sidebarSlice.push(groupCategory);
275
+ });
276
+ } else if (sidebarOptions.groupPathsBy === "tag") {
223
277
  sidebarSlice = groupByTags(
224
278
  api as ApiPageMetadata[],
225
279
  sidebarOptions,
@@ -228,5 +282,6 @@ export default function generateSidebarSlice(
228
282
  docPath
229
283
  );
230
284
  }
285
+
231
286
  return sidebarSlice;
232
287
  }
package/src/types.ts CHANGED
@@ -10,6 +10,7 @@ import type Request from "@paloaltonetworks/postman-collection";
10
10
  import {
11
11
  InfoObject,
12
12
  OperationObject,
13
+ SchemaObject,
13
14
  SecuritySchemeObject,
14
15
  TagObject,
15
16
  } from "./openapi/types";
@@ -44,12 +45,14 @@ export interface APIOptions {
44
45
  };
45
46
  proxy?: string;
46
47
  markdownGenerators?: MarkdownGenerator;
48
+ showSchemas?: boolean;
47
49
  }
48
50
 
49
51
  export interface MarkdownGenerator {
50
52
  createApiPageMD?: (pageData: ApiPageMetadata) => string;
51
53
  createInfoPageMD?: (pageData: InfoPageMetadata) => string;
52
54
  createTagPageMD?: (pageData: TagPageMetadata) => string;
55
+ createSchemaPageMD?: (pageData: SchemaPageMetadata) => string;
53
56
  }
54
57
 
55
58
  export interface SidebarOptions {
@@ -72,7 +75,11 @@ export interface LoadedContent {
72
75
  // loadedDocs: DocPageMetadata[]; TODO: cleanup
73
76
  }
74
77
 
75
- export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata;
78
+ export type ApiMetadata =
79
+ | ApiPageMetadata
80
+ | InfoPageMetadata
81
+ | TagPageMetadata
82
+ | SchemaPageMetadata;
76
83
 
77
84
  export interface ApiMetadataBase {
78
85
  sidebar?: string;
@@ -131,6 +138,19 @@ export interface TagPageMetadata extends ApiMetadataBase {
131
138
  markdown?: string;
132
139
  }
133
140
 
141
+ export interface SchemaPageMetadata extends ApiMetadataBase {
142
+ type: "schema";
143
+ schema: SchemaObject;
144
+ markdown?: string;
145
+ }
146
+
147
+ export interface TagGroupPageMetadata extends ApiMetadataBase {
148
+ type: "tagGroup";
149
+ name: string;
150
+ tags: TagObject[];
151
+ markdown?: string;
152
+ }
153
+
134
154
  export type ApiInfo = InfoObject;
135
155
 
136
156
  export interface ApiNavLink {