docusaurus-plugin-openapi-docs 1.1.3 → 1.1.6

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 (
@@ -568,13 +575,23 @@ function createEdges({
568
575
  return createDetailsNode(name, mergedSchemaName, mergedSchemas, required);
569
576
  }
570
577
 
578
+ // array of objects
579
+ if (mergedSchemas.items?.properties !== undefined) {
580
+ return createDetailsNode(name, mergedSchemaName, mergedSchemas, required);
581
+ }
582
+
583
+ if (mergedSchemas.writeOnly && mergedSchemas.writeOnly === true) {
584
+ return undefined;
585
+ }
586
+
571
587
  return create("SchemaItem", {
572
588
  collapsible: false,
573
589
  name,
574
- required,
590
+ required: false,
575
591
  schemaDescription: mergedSchemas.description,
576
592
  schemaName: schemaName,
577
593
  qualifierMessage: getQualifierMessage(schema),
594
+ defaultValue: mergedSchemas.default,
578
595
  });
579
596
  }
580
597
 
@@ -591,14 +608,19 @@ function createEdges({
591
608
  return createDetailsNode(name, schemaName, schema, required);
592
609
  }
593
610
 
611
+ if (schema.writeOnly && schema.writeOnly === true) {
612
+ return undefined;
613
+ }
614
+
594
615
  // primitives and array of non-objects
595
616
  return create("SchemaItem", {
596
617
  collapsible: false,
597
618
  name,
598
- required,
619
+ required: false,
599
620
  schemaDescription: schema.description,
600
621
  schemaName: schemaName,
601
622
  qualifierMessage: getQualifierMessage(schema),
623
+ defaultValue: schema.default,
602
624
  });
603
625
  }
604
626
 
@@ -627,7 +649,8 @@ function createNodes(schema: SchemaObject): any {
627
649
  return createProperties(schema);
628
650
  }
629
651
 
630
- if (schema.additionalProperties !== undefined) {
652
+ // Could be set to false to just check if evals to true
653
+ if (schema.additionalProperties) {
631
654
  return createAdditionalProperties(schema);
632
655
  }
633
656
 
@@ -678,11 +701,11 @@ interface Props {
678
701
  [key: string]: MediaTypeObject;
679
702
  };
680
703
  description?: string;
681
- required?: boolean;
704
+ required?: string[] | boolean;
682
705
  };
683
706
  }
684
707
 
685
- export function createSchemaDetails({ title, body, ...rest }: Props) {
708
+ export function createResponseSchema({ title, body, ...rest }: Props) {
686
709
  if (
687
710
  body === undefined ||
688
711
  body.content === undefined ||
@@ -692,64 +715,103 @@ export function createSchemaDetails({ title, body, ...rest }: Props) {
692
715
  return undefined;
693
716
  }
694
717
 
695
- // NOTE: We just pick a random content-type.
696
- // How common is it to have multiple?
697
- const randomFirstKey = Object.keys(body.content)[0];
698
- const firstBody = body.content[randomFirstKey].schema;
699
-
700
- if (firstBody === undefined) {
701
- return undefined;
702
- }
703
-
704
- // we don't show the table if there is no properties to show
705
- if (firstBody.properties !== undefined) {
706
- if (Object.keys(firstBody.properties).length === 0) {
707
- return undefined;
708
- }
709
- }
710
-
711
- // Root-level schema dropdown
712
- return createDetails({
713
- "data-collapsed": false,
714
- open: true,
715
- ...rest,
716
- children: [
717
- createDetailsSummary({
718
- style: { textAlign: "left" },
719
- children: [
720
- create("strong", { children: `${title}` }),
721
- guard(firstBody.type === "array", (format) =>
722
- create("span", {
723
- style: { opacity: "0.6" },
724
- children: ` array`,
725
- })
726
- ),
727
- guard(body.required, () => [
728
- create("strong", {
729
- style: {
730
- fontSize: "var(--ifm-code-font-size)",
731
- color: "var(--openapi-required)",
732
- },
733
- children: " required",
734
- }),
735
- ]),
736
- ],
737
- }),
738
- create("div", {
739
- style: { textAlign: "left", marginLeft: "1rem" },
740
- children: [
741
- guard(body.description, () => [
742
- create("div", {
743
- style: { marginTop: "1rem", marginBottom: "1rem" },
744
- 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
+ ],
745
809
  }),
746
- ]),
747
- ],
748
- }),
749
- create("ul", {
750
- style: { marginLeft: "1rem" },
751
- children: createNodes(firstBody),
810
+ ],
811
+ });
752
812
  }),
753
- ],
754
- });
813
+ });
814
+ }
815
+
816
+ return undefined;
755
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;