docusaurus-plugin-openapi-docs 1.1.4 → 1.1.7

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.
@@ -9,6 +9,10 @@ import { MediaTypeObject, SchemaObject } from "../openapi/types";
9
9
  import { createDescription } from "./createDescription";
10
10
  import { createDetails } from "./createDetails";
11
11
  import { createDetailsSummary } from "./createDetailsSummary";
12
+ import {
13
+ createResponseExample,
14
+ createResponseExamples,
15
+ } from "./createStatusCodes";
12
16
  import { getQualifierMessage, getSchemaName } from "./schema";
13
17
  import { create, guard } from "./utils";
14
18
 
@@ -227,8 +231,12 @@ function createItems(schema: SchemaObject) {
227
231
  }
228
232
 
229
233
  if (schema.items?.allOf !== undefined) {
230
- const { mergedSchemas }: { mergedSchemas: SchemaObject; required: any } =
231
- mergeAllOf(schema.items?.allOf);
234
+ // TODO: figure out if and how we should pass merged required array
235
+ const {
236
+ mergedSchemas,
237
+ }: { mergedSchemas: SchemaObject; required: string[] } = mergeAllOf(
238
+ schema.items?.allOf
239
+ );
232
240
 
233
241
  // Handles combo anyOf/oneOf + properties
234
242
  if (
@@ -387,7 +395,7 @@ function createDetailsNode(
387
395
  name: string,
388
396
  schemaName: string,
389
397
  schema: SchemaObject,
390
- required: any
398
+ required: string[] | boolean
391
399
  ): any {
392
400
  return create("SchemaItem", {
393
401
  collapsible: true,
@@ -402,7 +410,7 @@ function createDetailsNode(
402
410
  style: { opacity: "0.6" },
403
411
  children: ` ${schemaName}`,
404
412
  }),
405
- guard(required, () => [
413
+ guard(schema.required && schema.required === true, () => [
406
414
  create("strong", {
407
415
  style: {
408
416
  fontSize: "var(--ifm-code-font-size)",
@@ -446,7 +454,7 @@ function createPropertyDiscriminator(
446
454
  schemaName: string,
447
455
  schema: SchemaObject,
448
456
  discriminator: any,
449
- required: any
457
+ required: string[] | boolean
450
458
  ): any {
451
459
  if (schema === undefined) {
452
460
  return undefined;
@@ -515,7 +523,7 @@ function createPropertyDiscriminator(
515
523
  interface EdgeProps {
516
524
  name: string;
517
525
  schema: SchemaObject;
518
- required: boolean;
526
+ required: string[] | boolean;
519
527
  discriminator?: any | unknown;
520
528
  }
521
529
 
@@ -548,9 +556,8 @@ function createEdges({
548
556
  const {
549
557
  mergedSchemas,
550
558
  required,
551
- }: { mergedSchemas: SchemaObject; required: any } = mergeAllOf(
552
- schema.allOf
553
- );
559
+ }: { mergedSchemas: SchemaObject; required: string[] | boolean } =
560
+ mergeAllOf(schema.allOf);
554
561
  const mergedSchemaName = getSchemaName(mergedSchemas);
555
562
 
556
563
  if (
@@ -573,14 +580,18 @@ function createEdges({
573
580
  return createDetailsNode(name, mergedSchemaName, mergedSchemas, required);
574
581
  }
575
582
 
583
+ if (mergedSchemas.writeOnly && mergedSchemas.writeOnly === true) {
584
+ return undefined;
585
+ }
586
+
576
587
  return create("SchemaItem", {
577
588
  collapsible: false,
578
589
  name,
579
- required,
590
+ required: false,
580
591
  schemaDescription: mergedSchemas.description,
581
592
  schemaName: schemaName,
582
593
  qualifierMessage: getQualifierMessage(schema),
583
- defaultValue: schema.default,
594
+ defaultValue: mergedSchemas.default,
584
595
  });
585
596
  }
586
597
 
@@ -597,11 +608,15 @@ function createEdges({
597
608
  return createDetailsNode(name, schemaName, schema, required);
598
609
  }
599
610
 
611
+ if (schema.writeOnly && schema.writeOnly === true) {
612
+ return undefined;
613
+ }
614
+
600
615
  // primitives and array of non-objects
601
616
  return create("SchemaItem", {
602
617
  collapsible: false,
603
618
  name,
604
- required,
619
+ required: false,
605
620
  schemaDescription: schema.description,
606
621
  schemaName: schemaName,
607
622
  qualifierMessage: getQualifierMessage(schema),
@@ -634,7 +649,8 @@ function createNodes(schema: SchemaObject): any {
634
649
  return createProperties(schema);
635
650
  }
636
651
 
637
- if (schema.additionalProperties !== undefined) {
652
+ // Could be set to false to just check if evals to true
653
+ if (schema.additionalProperties) {
638
654
  return createAdditionalProperties(schema);
639
655
  }
640
656
 
@@ -685,11 +701,11 @@ interface Props {
685
701
  [key: string]: MediaTypeObject;
686
702
  };
687
703
  description?: string;
688
- required?: boolean;
704
+ required?: string[] | boolean;
689
705
  };
690
706
  }
691
707
 
692
- export function createSchemaDetails({ title, body, ...rest }: Props) {
708
+ export function createResponseSchema({ title, body, ...rest }: Props) {
693
709
  if (
694
710
  body === undefined ||
695
711
  body.content === undefined ||
@@ -699,64 +715,103 @@ export function createSchemaDetails({ title, body, ...rest }: Props) {
699
715
  return undefined;
700
716
  }
701
717
 
702
- // NOTE: We just pick a random content-type.
703
- // How common is it to have multiple?
704
- const randomFirstKey = Object.keys(body.content)[0];
705
- const firstBody = body.content[randomFirstKey].schema;
706
-
707
- if (firstBody === undefined) {
708
- return undefined;
709
- }
710
-
711
- // we don't show the table if there is no properties to show
712
- if (firstBody.properties !== undefined) {
713
- if (Object.keys(firstBody.properties).length === 0) {
714
- return undefined;
715
- }
716
- }
717
-
718
- // Root-level schema dropdown
719
- return createDetails({
720
- "data-collapsed": false,
721
- open: true,
722
- ...rest,
723
- 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" },
747
- children: [
748
- guard(body.description, () => [
749
- create("div", {
750
- style: { marginTop: "1rem", marginBottom: "1rem" },
751
- children: createDescription(body.description),
718
+ // Get all MIME types, including vendor-specific
719
+ const mimeTypes = Object.keys(body.content);
720
+
721
+ if (mimeTypes && mimeTypes.length) {
722
+ return create("MimeTabs", {
723
+ groupId: "mime-type",
724
+ children: mimeTypes.map((mimeType: any) => {
725
+ const responseExamples = body.content![mimeType].examples;
726
+ const responseExample = body.content![mimeType].example;
727
+ const firstBody = body.content![mimeType].schema;
728
+
729
+ if (
730
+ firstBody === undefined &&
731
+ responseExample === undefined &&
732
+ responseExamples === undefined
733
+ ) {
734
+ return undefined;
735
+ }
736
+
737
+ if (firstBody?.properties !== undefined) {
738
+ if (Object.keys(firstBody?.properties).length === 0) {
739
+ return undefined;
740
+ }
741
+ }
742
+
743
+ return create("TabItem", {
744
+ label: `${mimeType}`,
745
+ value: `${mimeType}`,
746
+ children: [
747
+ create("SchemaTabs", {
748
+ groupId: "schema-tabs",
749
+ children: [
750
+ firstBody &&
751
+ create("TabTtem", {
752
+ label: `${title}`,
753
+ value: `${title}`,
754
+ children: [
755
+ createDetails({
756
+ "data-collapsed": false,
757
+ open: true,
758
+ ...rest,
759
+ children: [
760
+ createDetailsSummary({
761
+ style: { textAlign: "left" },
762
+ children: [
763
+ create("strong", { children: `${title}` }),
764
+ guard(firstBody!.type === "array", (format) =>
765
+ create("span", {
766
+ style: { opacity: "0.6" },
767
+ children: ` array`,
768
+ })
769
+ ),
770
+ guard(
771
+ body.required && body.required === true,
772
+ () => [
773
+ create("strong", {
774
+ style: {
775
+ fontSize: "var(--ifm-code-font-size)",
776
+ color: "var(--openapi-required)",
777
+ },
778
+ children: " required",
779
+ }),
780
+ ]
781
+ ),
782
+ ],
783
+ }),
784
+ create("div", {
785
+ style: { textAlign: "left", marginLeft: "1rem" },
786
+ children: [
787
+ guard(body.description, () => [
788
+ create("div", {
789
+ style: {
790
+ marginTop: "1rem",
791
+ marginBottom: "1rem",
792
+ },
793
+ children: createDescription(body.description),
794
+ }),
795
+ ]),
796
+ ],
797
+ }),
798
+ create("ul", {
799
+ style: { marginLeft: "1rem" },
800
+ children: createNodes(firstBody!),
801
+ }),
802
+ ],
803
+ }),
804
+ ],
805
+ }),
806
+ responseExamples && createResponseExamples(responseExamples),
807
+ responseExample && createResponseExample(responseExample),
808
+ ],
752
809
  }),
753
- ]),
754
- ],
755
- }),
756
- create("ul", {
757
- style: { marginLeft: "1rem" },
758
- children: createNodes(firstBody),
810
+ ],
811
+ });
759
812
  }),
760
- ],
761
- });
813
+ });
814
+ }
815
+
816
+ return undefined;
762
817
  }
@@ -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
 
@@ -67,19 +67,30 @@ function createResponseHeaders(responseHeaders: any) {
67
67
  );
68
68
  }
69
69
 
70
- function createResponseExamples(responseExamples: any) {
70
+ export function createResponseExamples(responseExamples: any) {
71
71
  return Object.entries(responseExamples).map(
72
72
  ([exampleName, exampleValue]: any) => {
73
73
  const camelToSpaceName = exampleName.replace(/([A-Z])/g, " $1");
74
74
  let finalFormattedName =
75
75
  camelToSpaceName.charAt(0).toUpperCase() + camelToSpaceName.slice(1);
76
76
 
77
+ if (typeof exampleValue.value === "object") {
78
+ return create("TabItem", {
79
+ label: `${finalFormattedName}`,
80
+ value: `${finalFormattedName}`,
81
+ children: [
82
+ create("ResponseSamples", {
83
+ responseExample: JSON.stringify(exampleValue.value, null, 2),
84
+ }),
85
+ ],
86
+ });
87
+ }
77
88
  return create("TabItem", {
78
89
  label: `${finalFormattedName}`,
79
90
  value: `${finalFormattedName}`,
80
91
  children: [
81
92
  create("ResponseSamples", {
82
- responseExample: JSON.stringify(exampleValue.value, null, 2),
93
+ responseExample: exampleValue.value,
83
94
  }),
84
95
  ],
85
96
  });
@@ -87,6 +98,29 @@ function createResponseExamples(responseExamples: any) {
87
98
  );
88
99
  }
89
100
 
101
+ export function createResponseExample(responseExample: any) {
102
+ if (typeof responseExample === "object") {
103
+ return create("TabItem", {
104
+ label: `Example`,
105
+ value: `Example`,
106
+ children: [
107
+ create("ResponseSamples", {
108
+ responseExample: JSON.stringify(responseExample, null, 2),
109
+ }),
110
+ ],
111
+ });
112
+ }
113
+ return create("TabItem", {
114
+ label: `Example`,
115
+ value: `Example`,
116
+ children: [
117
+ create("ResponseSamples", {
118
+ responseExample: responseExample,
119
+ }),
120
+ ],
121
+ });
122
+ }
123
+
90
124
  export function createStatusCodes({ responses }: Props) {
91
125
  if (responses === undefined) {
92
126
  return undefined;
@@ -100,14 +134,10 @@ export function createStatusCodes({ responses }: Props) {
100
134
  return create("div", {
101
135
  children: [
102
136
  create("ApiTabs", {
137
+ // TODO: determine if we should persist status code selection
138
+ // groupId: "api-tabs",
103
139
  children: codes.map((code) => {
104
140
  const responseHeaders: any = responses[code].headers;
105
- const responseContent: any = responses[code].content;
106
- const responseContentKey: any =
107
- responseContent && Object.keys(responseContent)[0];
108
- const responseExamples: any =
109
- responseContentKey && responseContent[responseContentKey].examples;
110
-
111
141
  return create("TabItem", {
112
142
  label: code,
113
143
  value: code,
@@ -115,68 +145,30 @@ export function createStatusCodes({ responses }: Props) {
115
145
  create("div", {
116
146
  children: createDescription(responses[code].description),
117
147
  }),
118
- guard(responseExamples, () =>
119
- create("SchemaTabs", {
120
- children: [
121
- create("TabTtem", {
122
- label: "Schema",
123
- value: "Schema",
124
- children: [
125
- responseHeaders &&
126
- createDetails({
127
- "data-collaposed": false,
128
- open: true,
129
- style: { textAlign: "left" },
130
- children: [
131
- createDetailsSummary({
132
- children: [
133
- create("strong", {
134
- children: "Response Headers",
135
- }),
136
- ],
137
- }),
138
- createResponseHeaders(responseHeaders),
139
- ],
140
- }),
141
- create("div", {
142
- children: createSchemaDetails({
143
- title: "Schema",
144
- body: {
145
- content: responses[code].content,
146
- },
147
- }),
148
- }),
149
- ],
150
- }),
151
- createResponseExamples(responseExamples),
152
- ],
153
- })
154
- ),
155
- guard(responseHeaders, () =>
148
+ responseHeaders &&
156
149
  createDetails({
157
150
  "data-collaposed": false,
158
151
  open: true,
159
- style: { textAlign: "left" },
152
+ style: { textAlign: "left", marginBottom: "1rem" },
160
153
  children: [
161
154
  createDetailsSummary({
162
155
  children: [
163
- create("strong", { children: "Response Headers" }),
156
+ create("strong", {
157
+ children: "Response Headers",
158
+ }),
164
159
  ],
165
160
  }),
166
161
  createResponseHeaders(responseHeaders),
167
162
  ],
168
- })
169
- ),
170
- guard(!responseExamples, () =>
171
- create("div", {
172
- children: createSchemaDetails({
173
- title: "Schema",
174
- body: {
175
- content: responses[code].content,
176
- },
177
- }),
178
- })
179
- ),
163
+ }),
164
+ create("div", {
165
+ children: createResponseSchema({
166
+ title: "Schema",
167
+ body: {
168
+ content: responses[code].content,
169
+ },
170
+ }),
171
+ }),
180
172
  ],
181
173
  });
182
174
  }),
@@ -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;