docusaurus-plugin-openapi-docs 1.1.4 → 1.1.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.
@@ -227,8 +227,12 @@ function createItems(schema: SchemaObject) {
227
227
  }
228
228
 
229
229
  if (schema.items?.allOf !== undefined) {
230
- const { mergedSchemas }: { mergedSchemas: SchemaObject; required: any } =
231
- mergeAllOf(schema.items?.allOf);
230
+ // TODO: figure out if and how we should pass merged required array
231
+ const {
232
+ mergedSchemas,
233
+ }: { mergedSchemas: SchemaObject; required: string[] } = mergeAllOf(
234
+ schema.items?.allOf
235
+ );
232
236
 
233
237
  // Handles combo anyOf/oneOf + properties
234
238
  if (
@@ -387,7 +391,7 @@ function createDetailsNode(
387
391
  name: string,
388
392
  schemaName: string,
389
393
  schema: SchemaObject,
390
- required: any
394
+ required: string[] | boolean
391
395
  ): any {
392
396
  return create("SchemaItem", {
393
397
  collapsible: true,
@@ -402,7 +406,7 @@ function createDetailsNode(
402
406
  style: { opacity: "0.6" },
403
407
  children: ` ${schemaName}`,
404
408
  }),
405
- guard(required, () => [
409
+ guard(schema.required && schema.required === true, () => [
406
410
  create("strong", {
407
411
  style: {
408
412
  fontSize: "var(--ifm-code-font-size)",
@@ -446,7 +450,7 @@ function createPropertyDiscriminator(
446
450
  schemaName: string,
447
451
  schema: SchemaObject,
448
452
  discriminator: any,
449
- required: any
453
+ required: string[] | boolean
450
454
  ): any {
451
455
  if (schema === undefined) {
452
456
  return undefined;
@@ -515,7 +519,7 @@ function createPropertyDiscriminator(
515
519
  interface EdgeProps {
516
520
  name: string;
517
521
  schema: SchemaObject;
518
- required: boolean;
522
+ required: string[] | boolean;
519
523
  discriminator?: any | unknown;
520
524
  }
521
525
 
@@ -530,6 +534,8 @@ function createEdges({
530
534
  }: EdgeProps): any {
531
535
  const schemaName = getSchemaName(schema);
532
536
 
537
+ // if (name === "id") console.log(name, schema, required);
538
+
533
539
  if (discriminator !== undefined && discriminator.propertyName === name) {
534
540
  return createPropertyDiscriminator(
535
541
  name,
@@ -548,9 +554,8 @@ function createEdges({
548
554
  const {
549
555
  mergedSchemas,
550
556
  required,
551
- }: { mergedSchemas: SchemaObject; required: any } = mergeAllOf(
552
- schema.allOf
553
- );
557
+ }: { mergedSchemas: SchemaObject; required: string[] | boolean } =
558
+ mergeAllOf(schema.allOf);
554
559
  const mergedSchemaName = getSchemaName(mergedSchemas);
555
560
 
556
561
  if (
@@ -573,14 +578,18 @@ function createEdges({
573
578
  return createDetailsNode(name, mergedSchemaName, mergedSchemas, required);
574
579
  }
575
580
 
581
+ if (mergedSchemas.writeOnly && mergedSchemas.writeOnly === true) {
582
+ return undefined;
583
+ }
584
+
576
585
  return create("SchemaItem", {
577
586
  collapsible: false,
578
587
  name,
579
- required,
588
+ required: false,
580
589
  schemaDescription: mergedSchemas.description,
581
590
  schemaName: schemaName,
582
591
  qualifierMessage: getQualifierMessage(schema),
583
- defaultValue: schema.default,
592
+ defaultValue: mergedSchemas.default,
584
593
  });
585
594
  }
586
595
 
@@ -597,11 +606,15 @@ function createEdges({
597
606
  return createDetailsNode(name, schemaName, schema, required);
598
607
  }
599
608
 
609
+ if (schema.writeOnly && schema.writeOnly === true) {
610
+ return undefined;
611
+ }
612
+
600
613
  // primitives and array of non-objects
601
614
  return create("SchemaItem", {
602
615
  collapsible: false,
603
616
  name,
604
- required,
617
+ required: false,
605
618
  schemaDescription: schema.description,
606
619
  schemaName: schemaName,
607
620
  qualifierMessage: getQualifierMessage(schema),
@@ -685,11 +698,11 @@ interface Props {
685
698
  [key: string]: MediaTypeObject;
686
699
  };
687
700
  description?: string;
688
- required?: boolean;
701
+ required?: string[] | boolean;
689
702
  };
690
703
  }
691
704
 
692
- export function createSchemaDetails({ title, body, ...rest }: Props) {
705
+ export function createResponseSchema({ title, body, ...rest }: Props) {
693
706
  if (
694
707
  body === undefined ||
695
708
  body.content === undefined ||
@@ -699,8 +712,75 @@ export function createSchemaDetails({ title, body, ...rest }: Props) {
699
712
  return undefined;
700
713
  }
701
714
 
702
- // NOTE: We just pick a random content-type.
703
- // How common is it to have multiple?
715
+ // Get all MIME types, including vendor-specific
716
+ const mimeTypes = Object.keys(body.content);
717
+
718
+ if (mimeTypes && mimeTypes.length > 1) {
719
+ return create("MimeTabs", {
720
+ groupId: "mime-type",
721
+ children: mimeTypes.map((mimeType) => {
722
+ const firstBody = body.content![mimeType].schema;
723
+ if (firstBody === undefined) {
724
+ return undefined;
725
+ }
726
+ if (firstBody.properties !== undefined) {
727
+ if (Object.keys(firstBody.properties).length === 0) {
728
+ return undefined;
729
+ }
730
+ }
731
+ return create("TabItem", {
732
+ label: mimeType,
733
+ value: `${mimeType}`,
734
+ children: [
735
+ createDetails({
736
+ "data-collapsed": false,
737
+ open: true,
738
+ ...rest,
739
+ children: [
740
+ createDetailsSummary({
741
+ style: { textAlign: "left" },
742
+ children: [
743
+ create("strong", { children: `${title}` }),
744
+ guard(firstBody.type === "array", (format) =>
745
+ create("span", {
746
+ style: { opacity: "0.6" },
747
+ children: ` array`,
748
+ })
749
+ ),
750
+ guard(body.required && body.required === true, () => [
751
+ create("strong", {
752
+ style: {
753
+ fontSize: "var(--ifm-code-font-size)",
754
+ color: "var(--openapi-required)",
755
+ },
756
+ children: " required",
757
+ }),
758
+ ]),
759
+ ],
760
+ }),
761
+ create("div", {
762
+ style: { textAlign: "left", marginLeft: "1rem" },
763
+ children: [
764
+ guard(body.description, () => [
765
+ create("div", {
766
+ style: { marginTop: "1rem", marginBottom: "1rem" },
767
+ children: createDescription(body.description),
768
+ }),
769
+ ]),
770
+ ],
771
+ }),
772
+ create("ul", {
773
+ style: { marginLeft: "1rem" },
774
+ children: createNodes(firstBody),
775
+ }),
776
+ ],
777
+ }),
778
+ ],
779
+ });
780
+ }),
781
+ });
782
+ }
783
+
704
784
  const randomFirstKey = Object.keys(body.content)[0];
705
785
  const firstBody = body.content[randomFirstKey].schema;
706
786
 
@@ -714,49 +794,57 @@ export function createSchemaDetails({ title, body, ...rest }: Props) {
714
794
  return undefined;
715
795
  }
716
796
  }
717
-
718
- // Root-level schema dropdown
719
- return createDetails({
720
- "data-collapsed": false,
721
- open: true,
722
- ...rest,
797
+ return create("MimeTabs", {
723
798
  children: [
724
- createDetailsSummary({
725
- style: { textAlign: "left" },
726
- children: [
727
- create("strong", { children: `${title}` }),
728
- guard(firstBody.type === "array", (format) =>
729
- create("span", {
730
- style: { opacity: "0.6" },
731
- children: ` array`,
732
- })
733
- ),
734
- guard(body.required, () => [
735
- create("strong", {
736
- style: {
737
- fontSize: "var(--ifm-code-font-size)",
738
- color: "var(--openapi-required)",
739
- },
740
- children: " required",
741
- }),
742
- ]),
743
- ],
744
- }),
745
- create("div", {
746
- style: { textAlign: "left", marginLeft: "1rem" },
799
+ create("TabItem", {
800
+ label: randomFirstKey,
801
+ value: `${randomFirstKey}-schema`,
747
802
  children: [
748
- guard(body.description, () => [
749
- create("div", {
750
- style: { marginTop: "1rem", marginBottom: "1rem" },
751
- children: createDescription(body.description),
752
- }),
753
- ]),
803
+ createDetails({
804
+ "data-collapsed": false,
805
+ open: true,
806
+ ...rest,
807
+ children: [
808
+ createDetailsSummary({
809
+ style: { textAlign: "left" },
810
+ children: [
811
+ create("strong", { children: `${title}` }),
812
+ guard(firstBody.type === "array", (format) =>
813
+ create("span", {
814
+ style: { opacity: "0.6" },
815
+ children: ` array`,
816
+ })
817
+ ),
818
+ guard(body.required, () => [
819
+ create("strong", {
820
+ style: {
821
+ fontSize: "var(--ifm-code-font-size)",
822
+ color: "var(--openapi-required)",
823
+ },
824
+ children: " required",
825
+ }),
826
+ ]),
827
+ ],
828
+ }),
829
+ create("div", {
830
+ style: { textAlign: "left", marginLeft: "1rem" },
831
+ children: [
832
+ guard(body.description, () => [
833
+ create("div", {
834
+ style: { marginTop: "1rem", marginBottom: "1rem" },
835
+ children: createDescription(body.description),
836
+ }),
837
+ ]),
838
+ ],
839
+ }),
840
+ create("ul", {
841
+ style: { marginLeft: "1rem" },
842
+ children: createNodes(firstBody),
843
+ }),
844
+ ],
845
+ }),
754
846
  ],
755
847
  }),
756
- create("ul", {
757
- style: { marginLeft: "1rem" },
758
- children: createNodes(firstBody),
759
- }),
760
848
  ],
761
849
  });
762
850
  }
@@ -9,7 +9,7 @@ import { ApiItem } from "../types";
9
9
  import { createDescription } from "./createDescription";
10
10
  import { createDetails } from "./createDetails";
11
11
  import { createDetailsSummary } from "./createDetailsSummary";
12
- import { createSchemaDetails } from "./createSchemaDetails";
12
+ import { createResponseSchema } from "./createResponseSchema";
13
13
  import { create } from "./utils";
14
14
  import { guard } from "./utils";
15
15
 
@@ -79,7 +79,11 @@ function createResponseExamples(responseExamples: any) {
79
79
  value: `${finalFormattedName}`,
80
80
  children: [
81
81
  create("ResponseSamples", {
82
- responseExample: JSON.stringify(exampleValue.value, null, 2),
82
+ responseExample: JSON.stringify(
83
+ exampleValue.value ?? exampleValue,
84
+ null,
85
+ 2
86
+ ),
83
87
  }),
84
88
  ],
85
89
  });
@@ -106,7 +110,9 @@ export function createStatusCodes({ responses }: Props) {
106
110
  const responseContentKey: any =
107
111
  responseContent && Object.keys(responseContent)[0];
108
112
  const responseExamples: any =
109
- responseContentKey && responseContent[responseContentKey].examples;
113
+ responseContentKey &&
114
+ (responseContent[responseContentKey].examples ||
115
+ responseContent[responseContentKey].example);
110
116
 
111
117
  return create("TabItem", {
112
118
  label: code,
@@ -126,7 +132,7 @@ export function createStatusCodes({ responses }: Props) {
126
132
  createDetails({
127
133
  "data-collaposed": false,
128
134
  open: true,
129
- style: { textAlign: "left" },
135
+ style: { textAlign: "left", marginBottom: "1rem" },
130
136
  children: [
131
137
  createDetailsSummary({
132
138
  children: [
@@ -139,7 +145,7 @@ export function createStatusCodes({ responses }: Props) {
139
145
  ],
140
146
  }),
141
147
  create("div", {
142
- children: createSchemaDetails({
148
+ children: createResponseSchema({
143
149
  title: "Schema",
144
150
  body: {
145
151
  content: responses[code].content,
@@ -152,7 +158,7 @@ export function createStatusCodes({ responses }: Props) {
152
158
  ],
153
159
  })
154
160
  ),
155
- guard(responseHeaders, () =>
161
+ guard(responseHeaders && !responseExamples, () =>
156
162
  createDetails({
157
163
  "data-collaposed": false,
158
164
  open: true,
@@ -169,7 +175,7 @@ export function createStatusCodes({ responses }: Props) {
169
175
  ),
170
176
  guard(!responseExamples, () =>
171
177
  create("div", {
172
- children: createSchemaDetails({
178
+ children: createResponseSchema({
173
179
  title: "Schema",
174
180
  body: {
175
181
  content: responses[code].content,
@@ -10,6 +10,7 @@ import { escape } from "lodash";
10
10
  import {
11
11
  ContactObject,
12
12
  LicenseObject,
13
+ MediaTypeObject,
13
14
  SecuritySchemeObject,
14
15
  } from "../openapi/types";
15
16
  import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
@@ -26,6 +27,17 @@ import { createTermsOfService } from "./createTermsOfService";
26
27
  import { createVersionBadge } from "./createVersionBadge";
27
28
  import { render } from "./utils";
28
29
 
30
+ interface Props {
31
+ title: string;
32
+ body: {
33
+ content?: {
34
+ [key: string]: MediaTypeObject;
35
+ };
36
+ description?: string;
37
+ required?: boolean;
38
+ };
39
+ }
40
+
29
41
  export function createApiPageMD({
30
42
  title,
31
43
  api: {
@@ -39,6 +51,7 @@ export function createApiPageMD({
39
51
  }: ApiPageMetadata) {
40
52
  return render([
41
53
  `import ApiTabs from "@theme/ApiTabs";\n`,
54
+ `import MimeTabs from "@theme/MimeTabs";\n`,
42
55
  `import ParamsItem from "@theme/ParamsItem";\n`,
43
56
  `import ResponseSamples from "@theme/ResponseSamples";\n`,
44
57
  `import SchemaItem from "@theme/SchemaItem"\n`,
@@ -52,7 +65,10 @@ export function createApiPageMD({
52
65
  createParamsDetails({ parameters, type: "query" }),
53
66
  createParamsDetails({ parameters, type: "header" }),
54
67
  createParamsDetails({ parameters, type: "cookie" }),
55
- createRequestBodyDetails({ title: "Request Body", body: requestBody }),
68
+ createRequestBodyDetails({
69
+ title: "Request Body",
70
+ body: requestBody,
71
+ } as Props),
56
72
  createStatusCodes({ responses }),
57
73
  ]);
58
74
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import chalk from "chalk";
9
9
 
10
+ import { mergeAllOf } from "../markdown/createRequestSchema";
10
11
  import { SchemaObject } from "./types";
11
12
 
12
13
  interface OASTypeToTypeMap {
@@ -29,6 +30,7 @@ const primitives: Primitives = {
29
30
  default: () => "string",
30
31
  email: () => "user@example.com",
31
32
  date: () => new Date().toISOString().substring(0, 10),
33
+ "date-time": () => new Date().toISOString().substring(0, 10),
32
34
  uuid: () => "3fa85f64-5717-4562-b3fc-2c963f66afa6",
33
35
  hostname: () => "example.com",
34
36
  ipv4: () => "198.51.100.42",
@@ -58,21 +60,16 @@ export const sampleFromSchema = (schema: SchemaObject = {}): any => {
58
60
  }
59
61
 
60
62
  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
- };
63
+ const { mergedSchemas }: { mergedSchemas: SchemaObject } =
64
+ mergeAllOf(allOf);
65
+ if (mergedSchemas.properties) {
66
+ for (const [key, value] of Object.entries(mergedSchemas.properties)) {
67
+ if (value.readOnly && value.readOnly === true) {
68
+ delete mergedSchemas.properties[key];
69
+ }
73
70
  }
74
71
  }
75
- return sampleFromSchema(obj);
72
+ return sampleFromSchema(mergedSchemas);
76
73
  }
77
74
 
78
75
  if (!type) {
@@ -88,6 +85,22 @@ export const sampleFromSchema = (schema: SchemaObject = {}): any => {
88
85
  if (type === "object") {
89
86
  let obj: any = {};
90
87
  for (let [name, prop] of Object.entries(properties ?? {})) {
88
+ if (prop.properties) {
89
+ for (const [key, value] of Object.entries(prop.properties)) {
90
+ if (value.readOnly && value.readOnly === true) {
91
+ delete prop.properties[key];
92
+ }
93
+ }
94
+ }
95
+
96
+ if (prop.items && prop.items.properties) {
97
+ for (const [key, value] of Object.entries(prop.items.properties)) {
98
+ if (value.readOnly && value.readOnly === true) {
99
+ delete prop.items.properties[key];
100
+ }
101
+ }
102
+ }
103
+
91
104
  if (prop.deprecated) {
92
105
  continue;
93
106
  }
@@ -115,6 +128,10 @@ export const sampleFromSchema = (schema: SchemaObject = {}): any => {
115
128
  return normalizeArray(schema.enum)[0];
116
129
  }
117
130
 
131
+ if (schema.readOnly && schema.readOnly === true) {
132
+ return undefined;
133
+ }
134
+
118
135
  return primitive(schema);
119
136
  } catch (err) {
120
137
  console.error(
@@ -131,7 +148,7 @@ function primitive(schema: SchemaObject = {}) {
131
148
  return;
132
149
  }
133
150
 
134
- let fn = primitives[type].default;
151
+ let fn = schema.default ? () => schema.default : primitives[type].default;
135
152
 
136
153
  if (format !== undefined) {
137
154
  fn = primitives[type][format] || fn;
@@ -124,9 +124,7 @@ function createItems(
124
124
  securitySchemes: openapiData.components?.securitySchemes,
125
125
  info: {
126
126
  ...openapiData.info,
127
- tags: openapiData.tags?.map((tagName) =>
128
- getTagDisplayName(tagName.name!, openapiData.tags ?? [])
129
- ),
127
+ tags: openapiData.tags,
130
128
  title: openapiData.info.title ?? "Introduction",
131
129
  logo: openapiData.info["x-logo"]! as any,
132
130
  darkLogo: openapiData.info["x-dark-logo"]! as any,
@@ -175,6 +173,18 @@ function createItems(
175
173
  jsonRequestBodyExample = sampleFromSchema(body.schema);
176
174
  }
177
175
 
176
+ // Handle vendor JSON media types
177
+ const bodyContent = operationObject.requestBody?.content;
178
+ if (bodyContent) {
179
+ const firstBodyContentKey = Object.keys(bodyContent)[0];
180
+ if (firstBodyContentKey.endsWith("+json")) {
181
+ const firstBody = bodyContent[firstBodyContentKey];
182
+ if (firstBody?.schema) {
183
+ jsonRequestBodyExample = sampleFromSchema(firstBody.schema);
184
+ }
185
+ }
186
+ }
187
+
178
188
  // TODO: Don't include summary temporarilly
179
189
  const { summary, ...defaults } = operationObject;
180
190
 
@@ -188,9 +198,7 @@ function createItems(
188
198
  frontMatter: {},
189
199
  api: {
190
200
  ...defaults,
191
- tags: operationObject.tags?.map((tagName) =>
192
- getTagDisplayName(tagName, openapiData.tags ?? [])
193
- ),
201
+ tags: operationObject.tags,
194
202
  method,
195
203
  path,
196
204
  servers,
@@ -291,7 +299,7 @@ export async function readOpenapiFiles(
291
299
  export async function processOpenapiFiles(
292
300
  files: OpenApiFiles[],
293
301
  sidebarOptions: SidebarOptions
294
- ): Promise<[ApiMetadata[], TagObject[]]> {
302
+ ): Promise<[ApiMetadata[], TagObject[][]]> {
295
303
  const promises = files.map(async (file) => {
296
304
  if (file.data !== undefined) {
297
305
  const processedFile = await processOpenapiFile(file.data, sidebarOptions);
@@ -326,7 +334,7 @@ export async function processOpenapiFiles(
326
334
  // Remove undefined tags due to transient parsing errors
327
335
  return x !== undefined;
328
336
  });
329
- return [items as ApiMetadata[], tags as TagObject[]];
337
+ return [items as ApiMetadata[], tags as TagObject[][]];
330
338
  }
331
339
 
332
340
  export async function processOpenapiFile(
@@ -41,7 +41,7 @@ export interface InfoObject {
41
41
  contact?: ContactObject;
42
42
  license?: LicenseObject;
43
43
  version: string;
44
- tags?: String[];
44
+ tags?: TagObject[];
45
45
  "x-logo"?: LogoObject;
46
46
  "x-dark-logo"?: LogoObject;
47
47
  logo?: LogoObject;
@@ -37,7 +37,7 @@ function groupByTags(
37
37
  items: ApiPageMetadata[],
38
38
  sidebarOptions: SidebarOptions,
39
39
  options: APIOptions,
40
- tags: TagObject[],
40
+ tags: TagObject[][],
41
41
  docPath: string
42
42
  ): ProcessedSidebar {
43
43
  const { outputDir, label } = options;
@@ -60,12 +60,20 @@ function groupByTags(
60
60
  });
61
61
 
62
62
  // TODO: make sure we only take the first tag
63
- const apiTags = uniq(
63
+ const operationTags = uniq(
64
64
  apiItems
65
65
  .flatMap((item) => item.api.tags)
66
66
  .filter((item): item is string => !!item)
67
67
  );
68
68
 
69
+ // Only include operation tags that are globally defined
70
+ const apiTags: string[] = [];
71
+ tags.flat().forEach((tag) => {
72
+ if (operationTags.includes(tag.name!)) {
73
+ apiTags.push(tag.name!);
74
+ }
75
+ });
76
+
69
77
  const basePath = docPath
70
78
  ? outputDir.split(docPath!)[1].replace(/^\/+/g, "")
71
79
  : outputDir.slice(outputDir.indexOf("/", 1)).replace(/^\/+/g, "");
@@ -107,7 +115,7 @@ function groupByTags(
107
115
  );
108
116
  const tagObject = tags.flat().find(
109
117
  (t) =>
110
- (tag === t.name || tag === t["x-displayName"]) ?? {
118
+ tag === t.name ?? {
111
119
  name: tag,
112
120
  description: `${tag} Index`,
113
121
  }
@@ -148,7 +156,7 @@ function groupByTags(
148
156
 
149
157
  return {
150
158
  type: "category" as const,
151
- label: tag,
159
+ label: tagObject?.["x-displayName"] ?? tag,
152
160
  link: linkConfig,
153
161
  collapsible: sidebarCollapsible,
154
162
  collapsed: sidebarCollapsed,
@@ -191,7 +199,7 @@ export default function generateSidebarSlice(
191
199
  sidebarOptions: SidebarOptions,
192
200
  options: APIOptions,
193
201
  api: ApiMetadata[],
194
- tags: TagObject[],
202
+ tags: TagObject[][],
195
203
  docPath: string
196
204
  ) {
197
205
  let sidebarSlice: ProcessedSidebar = [];