docusaurus-plugin-openapi-docs 0.0.0-406 → 0.0.0-409

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.
@@ -86,12 +86,14 @@ function createAnyOneOf(schema) {
86
86
  });
87
87
  }
88
88
  function createProperties(schema) {
89
+ const discriminator = schema.discriminator;
89
90
  return Object.entries(schema.properties).map(([key, val]) => createEdges({
90
91
  name: key,
91
92
  schema: val,
92
93
  required: Array.isArray(schema.required)
93
94
  ? schema.required.includes(key)
94
95
  : false,
96
+ discriminator,
95
97
  }));
96
98
  }
97
99
  function createAdditionalProperties(schema) {
@@ -228,6 +230,93 @@ function createItems(schema) {
228
230
  : false,
229
231
  }));
230
232
  }
233
+ /**
234
+ * For handling discriminators that do not map to a same-level property
235
+ */
236
+ function createDiscriminator(schema) {
237
+ var _a;
238
+ const discriminator = schema.discriminator;
239
+ const propertyName = discriminator === null || discriminator === void 0 ? void 0 : discriminator.propertyName;
240
+ const propertyType = "string"; // should always be string
241
+ const mapping = discriminator === null || discriminator === void 0 ? void 0 : discriminator.mapping;
242
+ // Explicit mapping is required since we can't support implicit
243
+ if (mapping === undefined) {
244
+ return undefined;
245
+ }
246
+ // Attempt to get the property description we want to display
247
+ // TODO: how to make it predictable when handling allOf
248
+ let propertyDescription;
249
+ const firstMappingSchema = mapping[Object.keys(mapping)[0]];
250
+ if (firstMappingSchema.properties !== undefined) {
251
+ propertyDescription =
252
+ firstMappingSchema.properties[propertyName].description;
253
+ }
254
+ if (firstMappingSchema.allOf !== undefined) {
255
+ const { mergedSchemas } = mergeAllOf(firstMappingSchema.allOf);
256
+ if (mergedSchemas.properties !== undefined) {
257
+ propertyDescription =
258
+ (_a = mergedSchemas.properties[propertyName]) === null || _a === void 0 ? void 0 : _a.description;
259
+ }
260
+ }
261
+ if (propertyDescription === undefined) {
262
+ if (schema.properties !== undefined &&
263
+ schema.properties[propertyName] !== undefined) {
264
+ propertyDescription = schema.properties[propertyName].description;
265
+ }
266
+ }
267
+ return (0, utils_1.create)("li", {
268
+ className: "discriminatorItem",
269
+ children: (0, utils_1.create)("div", {
270
+ children: [
271
+ (0, utils_1.create)("strong", {
272
+ style: { paddingLeft: "1rem" },
273
+ children: propertyName,
274
+ }),
275
+ (0, utils_1.guard)(propertyType, (name) => (0, utils_1.create)("span", {
276
+ style: { opacity: "0.6" },
277
+ children: ` ${propertyType}`,
278
+ })),
279
+ (0, utils_1.guard)((0, schema_1.getQualifierMessage)(schema.discriminator), (message) => (0, utils_1.create)("div", {
280
+ style: {
281
+ paddingLeft: "1rem",
282
+ },
283
+ children: (0, createDescription_1.createDescription)(message),
284
+ })),
285
+ (0, utils_1.guard)(propertyDescription, (description) => (0, utils_1.create)("div", {
286
+ style: {
287
+ paddingLeft: "1rem",
288
+ },
289
+ children: (0, createDescription_1.createDescription)(description),
290
+ })),
291
+ (0, utils_1.create)("DiscriminatorTabs", {
292
+ children: Object.keys(mapping).map((key, index) => {
293
+ if (mapping[key].allOf !== undefined) {
294
+ const { mergedSchemas } = mergeAllOf(mapping[key].allOf);
295
+ // Cleanup duplicate property from mapping schema
296
+ delete mergedSchemas.properties[propertyName];
297
+ mapping[key] = mergedSchemas;
298
+ }
299
+ if (mapping[key].properties !== undefined) {
300
+ // Cleanup duplicate property from mapping schema
301
+ delete mapping[key].properties[propertyName];
302
+ }
303
+ const label = key;
304
+ return (0, utils_1.create)("TabItem", {
305
+ label: label,
306
+ value: `${index}-item-discriminator`,
307
+ children: [
308
+ (0, utils_1.create)("div", {
309
+ style: { marginLeft: "-4px" },
310
+ children: createNodes(mapping[key]),
311
+ }),
312
+ ],
313
+ });
314
+ }),
315
+ }),
316
+ ],
317
+ }),
318
+ });
319
+ }
231
320
  function createDetailsNode(name, schemaName, schema, required) {
232
321
  return (0, utils_1.create)("SchemaItem", {
233
322
  collapsible: true,
@@ -272,12 +361,75 @@ function createDetailsNode(name, schemaName, schema, required) {
272
361
  ],
273
362
  });
274
363
  }
364
+ /**
365
+ * For handling discriminators that map to a same-level property (like 'petType').
366
+ * Note: These should only be encountered while iterating through properties.
367
+ */
368
+ function createPropertyDiscriminator(name, schemaName, schema, discriminator, required) {
369
+ if (schema === undefined) {
370
+ return undefined;
371
+ }
372
+ if (discriminator.mapping === undefined) {
373
+ return undefined;
374
+ }
375
+ return (0, utils_1.create)("li", {
376
+ className: "discriminatorItem",
377
+ children: (0, utils_1.create)("div", {
378
+ children: [
379
+ (0, utils_1.create)("strong", { style: { paddingLeft: "1rem" }, children: name }),
380
+ (0, utils_1.guard)(schemaName, (name) => (0, utils_1.create)("span", {
381
+ style: { opacity: "0.6" },
382
+ children: ` ${schemaName}`,
383
+ })),
384
+ (0, utils_1.guard)(required, () => [
385
+ (0, utils_1.create)("strong", {
386
+ style: {
387
+ fontSize: "var(--ifm-code-font-size)",
388
+ color: "var(--openapi-required)",
389
+ },
390
+ children: " required",
391
+ }),
392
+ ]),
393
+ (0, utils_1.guard)((0, schema_1.getQualifierMessage)(discriminator), (message) => (0, utils_1.create)("div", {
394
+ style: {
395
+ paddingLeft: "1rem",
396
+ },
397
+ children: (0, createDescription_1.createDescription)(message),
398
+ })),
399
+ (0, utils_1.guard)(schema.description, (description) => (0, utils_1.create)("div", {
400
+ style: {
401
+ paddingLeft: "1rem",
402
+ },
403
+ children: (0, createDescription_1.createDescription)(description),
404
+ })),
405
+ (0, utils_1.create)("DiscriminatorTabs", {
406
+ children: Object.keys(discriminator === null || discriminator === void 0 ? void 0 : discriminator.mapping).map((key, index) => {
407
+ const label = key;
408
+ return (0, utils_1.create)("TabItem", {
409
+ label: label,
410
+ value: `${index}-item-discriminator`,
411
+ children: [
412
+ (0, utils_1.create)("div", {
413
+ style: { marginLeft: "-4px" },
414
+ children: createNodes(discriminator === null || discriminator === void 0 ? void 0 : discriminator.mapping[key]),
415
+ }),
416
+ ],
417
+ });
418
+ }),
419
+ }),
420
+ ],
421
+ }),
422
+ });
423
+ }
275
424
  /**
276
425
  * Creates the edges or "leaves" of a schema tree. Edges can branch into sub-nodes with createDetails().
277
426
  */
278
- function createEdges({ name, schema, required }) {
427
+ function createEdges({ name, schema, required, discriminator, }) {
279
428
  var _a;
280
429
  const schemaName = (0, schema_1.getSchemaName)(schema);
430
+ if (discriminator !== undefined && discriminator.propertyName === name) {
431
+ return createPropertyDiscriminator(name, "string", schema, discriminator, required);
432
+ }
281
433
  if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
282
434
  return createDetailsNode(name, schemaName, schema, required);
283
435
  }
@@ -327,6 +479,9 @@ function createEdges({ name, schema, required }) {
327
479
  * Creates a hierarchical level of a schema tree. Nodes produce edges that can branch into sub-nodes with edges, recursively.
328
480
  */
329
481
  function createNodes(schema) {
482
+ if (schema.discriminator !== undefined) {
483
+ return createDiscriminator(schema);
484
+ }
330
485
  if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
331
486
  return createAnyOneOf(schema);
332
487
  }
@@ -25,6 +25,7 @@ function createApiPageMD({ title, api: { deprecated, "x-deprecated-description":
25
25
  `import SchemaItem from "@theme/SchemaItem"\n`,
26
26
  `import ApiTabs from "@theme/ApiTabs";\n`,
27
27
  `import SchemaTabs from "@theme/SchemaTabs";\n`,
28
+ `import DiscriminatorTabs from "@theme/DiscriminatorTabs";\n`,
28
29
  `import TabItem from "@theme/TabItem";\n\n`,
29
30
  `## ${(0, lodash_1.escape)(title)}\n\n`,
30
31
  (0, createDeprecationNotice_1.createDeprecationNotice)({ deprecated, description: deprecatedDescription }),
@@ -61,12 +61,25 @@ function getQualifierMessage(schema) {
61
61
  let qualifierGroups = [];
62
62
  if (schema.minLength || schema.maxLength) {
63
63
  let lengthQualifier = "";
64
- if (schema.minLength) {
65
- lengthQualifier += `${schema.minLength} ≤ `;
64
+ let minLength;
65
+ let maxLength;
66
+ if (schema.minLength && schema.minLength > 1) {
67
+ minLength = `\`>= ${schema.minLength} characters\``;
68
+ }
69
+ if (schema.minLength && schema.minLength === 1) {
70
+ minLength = `\`non-empty\``;
66
71
  }
67
- lengthQualifier += "length";
68
72
  if (schema.maxLength) {
69
- lengthQualifier += ` ${schema.maxLength}`;
73
+ maxLength = `\`<= ${schema.maxLength} characters\``;
74
+ }
75
+ if (minLength && !maxLength) {
76
+ lengthQualifier += minLength;
77
+ }
78
+ if (maxLength && !minLength) {
79
+ lengthQualifier += maxLength;
80
+ }
81
+ if (minLength && maxLength) {
82
+ lengthQualifier += `${minLength} and ${maxLength}`;
70
83
  }
71
84
  qualifierGroups.push(lengthQualifier);
72
85
  }
@@ -75,38 +88,54 @@ function getQualifierMessage(schema) {
75
88
  typeof schema.exclusiveMinimum === "number" ||
76
89
  typeof schema.exclusiveMaximum === "number") {
77
90
  let minmaxQualifier = "";
91
+ let minimum;
92
+ let maximum;
78
93
  if (typeof schema.exclusiveMinimum === "number") {
79
- minmaxQualifier += `${schema.exclusiveMinimum} < `;
94
+ minimum = `\`> ${schema.exclusiveMinimum}\``;
80
95
  }
81
96
  else if (schema.minimum && !schema.exclusiveMinimum) {
82
- minmaxQualifier += `${schema.minimum} ≤ `;
97
+ minimum = `\`>= ${schema.minimum}\``;
83
98
  }
84
99
  else if (schema.minimum && schema.exclusiveMinimum === true) {
85
- minmaxQualifier += `${schema.minimum} < `;
100
+ minimum = `\`> ${schema.minimum}\``;
86
101
  }
87
- minmaxQualifier += "value";
88
102
  if (typeof schema.exclusiveMaximum === "number") {
89
- minmaxQualifier += ` < ${schema.exclusiveMaximum}`;
103
+ maximum = `\`< ${schema.exclusiveMaximum}\``;
90
104
  }
91
105
  else if (schema.maximum && !schema.exclusiveMaximum) {
92
- minmaxQualifier += ` ${schema.maximum}`;
106
+ maximum = `\`<= ${schema.maximum}\``;
93
107
  }
94
108
  else if (schema.maximum && schema.exclusiveMaximum === true) {
95
- minmaxQualifier += ` < ${schema.maximum}`;
109
+ maximum = `\`< ${schema.maximum}\``;
110
+ }
111
+ if (minimum && !maximum) {
112
+ minmaxQualifier += minimum;
113
+ }
114
+ if (maximum && !minimum) {
115
+ minmaxQualifier += maximum;
116
+ }
117
+ if (minimum && maximum) {
118
+ minmaxQualifier += `${minimum} and ${maximum}`;
96
119
  }
97
120
  qualifierGroups.push(minmaxQualifier);
98
121
  }
99
122
  if (schema.pattern) {
100
123
  qualifierGroups.push(`Value must match regular expression \`${schema.pattern}\``);
101
124
  }
125
+ // Check if discriminator mapping
126
+ const discriminator = schema;
127
+ if (discriminator.mapping) {
128
+ const values = Object.keys(discriminator.mapping);
129
+ qualifierGroups.push(`[${values.map((e) => `\`${e}\``).join(", ")}]`);
130
+ }
102
131
  if (schema.enum) {
103
132
  qualifierGroups.push(`[${schema.enum.map((e) => `\`${e}\``).join(", ")}]`);
104
133
  }
105
134
  if (schema.minItems) {
106
- qualifierGroups.push(`items >= ${schema.minItems}`);
135
+ qualifierGroups.push(`\`>= ${schema.minItems}\``);
107
136
  }
108
137
  if (schema.maxItems) {
109
- qualifierGroups.push(`items <= ${schema.maxItems}`);
138
+ qualifierGroups.push(`\`<= ${schema.maxItems}\``);
110
139
  }
111
140
  if (qualifierGroups.length === 0) {
112
141
  return undefined;
@@ -16,17 +16,17 @@ describe("getQualifierMessage", () => {
16
16
  // minLength + maxLength
17
17
  //
18
18
  it("should render minLength", () => {
19
- const expected = "**Possible values:** 1 ≤ length";
19
+ const expected = "**Possible values:** `non-empty`";
20
20
  const actual = (0, schema_1.getQualifierMessage)({ minLength: 1 });
21
21
  expect(actual).toBe(expected);
22
22
  });
23
23
  it("should render maxLength", () => {
24
- const expected = "**Possible values:** length 40";
24
+ const expected = "**Possible values:** `<= 40 characters`";
25
25
  const actual = (0, schema_1.getQualifierMessage)({ maxLength: 40 });
26
26
  expect(actual).toBe(expected);
27
27
  });
28
28
  it("should render minLength and maxLength", () => {
29
- const expected = "**Possible values:** 1 length 40";
29
+ const expected = "**Possible values:** `non-empty` and `<= 40 characters`";
30
30
  const actual = (0, schema_1.getQualifierMessage)({ minLength: 1, maxLength: 40 });
31
31
  expect(actual).toBe(expected);
32
32
  });
@@ -39,7 +39,7 @@ describe("getQualifierMessage", () => {
39
39
  expect(actual).toBe(expected);
40
40
  });
41
41
  it("should render multiple string qualifiers", () => {
42
- const expected = "**Possible values:** 1 length 40, Value must match regular expression `^[a-zA-Z0-9_-]*$`";
42
+ const expected = "**Possible values:** `non-empty` and `<= 40 characters`, Value must match regular expression `^[a-zA-Z0-9_-]*$`";
43
43
  const actual = (0, schema_1.getQualifierMessage)({
44
44
  minLength: 1,
45
45
  maxLength: 40,
@@ -59,42 +59,42 @@ describe("getQualifierMessage", () => {
59
59
  // minimum + maximum + exclusiveMinimum + exclusiveMaximum
60
60
  //
61
61
  it("should render minimum", () => {
62
- const expected = "**Possible values:** 1 ≤ value";
62
+ const expected = "**Possible values:** `>= 1`";
63
63
  const actual = (0, schema_1.getQualifierMessage)({ minimum: 1 });
64
64
  expect(actual).toBe(expected);
65
65
  });
66
66
  it("should render maximum", () => {
67
- const expected = "**Possible values:** value 40";
67
+ const expected = "**Possible values:** `<= 40`";
68
68
  const actual = (0, schema_1.getQualifierMessage)({ maximum: 40 });
69
69
  expect(actual).toBe(expected);
70
70
  });
71
71
  it("should render numeric exclusiveMinimum", () => {
72
- const expected = "**Possible values:** 1 < value";
72
+ const expected = "**Possible values:** `> 1`";
73
73
  const actual = (0, schema_1.getQualifierMessage)({ exclusiveMinimum: 1 });
74
74
  expect(actual).toBe(expected);
75
75
  });
76
76
  it("should render numeric exclusiveMaximum", () => {
77
- const expected = "**Possible values:** value < 40";
77
+ const expected = "**Possible values:** `< 40`";
78
78
  const actual = (0, schema_1.getQualifierMessage)({ exclusiveMaximum: 40 });
79
79
  expect(actual).toBe(expected);
80
80
  });
81
81
  it("should render boolean exclusiveMinimum", () => {
82
- const expected = "**Possible values:** 1 < value";
82
+ const expected = "**Possible values:** `> 1`";
83
83
  const actual = (0, schema_1.getQualifierMessage)({ minimum: 1, exclusiveMinimum: true });
84
84
  expect(actual).toBe(expected);
85
85
  });
86
86
  it("should render boolean exclusiveMaximum", () => {
87
- const expected = "**Possible values:** value < 40";
87
+ const expected = "**Possible values:** `< 40`";
88
88
  const actual = (0, schema_1.getQualifierMessage)({ maximum: 40, exclusiveMaximum: true });
89
89
  expect(actual).toBe(expected);
90
90
  });
91
91
  it("should render minimum when exclusiveMinimum is false", () => {
92
- const expected = "**Possible values:** 1 ≤ value";
92
+ const expected = "**Possible values:** `>= 1`";
93
93
  const actual = (0, schema_1.getQualifierMessage)({ minimum: 1, exclusiveMinimum: false });
94
94
  expect(actual).toBe(expected);
95
95
  });
96
96
  it("should render maximum when exclusiveMaximum is false", () => {
97
- const expected = "**Possible values:** value 40";
97
+ const expected = "**Possible values:** `<= 40`";
98
98
  const actual = (0, schema_1.getQualifierMessage)({
99
99
  maximum: 40,
100
100
  exclusiveMaximum: false,
@@ -102,12 +102,12 @@ describe("getQualifierMessage", () => {
102
102
  expect(actual).toBe(expected);
103
103
  });
104
104
  it("should render minimum and maximum", () => {
105
- const expected = "**Possible values:** 1 value 40";
105
+ const expected = "**Possible values:** `>= 1` and `<= 40`";
106
106
  const actual = (0, schema_1.getQualifierMessage)({ minimum: 1, maximum: 40 });
107
107
  expect(actual).toBe(expected);
108
108
  });
109
109
  it("should render boolean exclusiveMinimum and maximum", () => {
110
- const expected = "**Possible values:** 1 < value 40";
110
+ const expected = "**Possible values:** `> 1` and `<= 40`";
111
111
  const actual = (0, schema_1.getQualifierMessage)({
112
112
  minimum: 1,
113
113
  maximum: 40,
@@ -116,7 +116,7 @@ describe("getQualifierMessage", () => {
116
116
  expect(actual).toBe(expected);
117
117
  });
118
118
  it("should render minimum and boolean exclusiveMaximum", () => {
119
- const expected = "**Possible values:** 1 value < 40";
119
+ const expected = "**Possible values:** `>= 1` and `< 40`";
120
120
  const actual = (0, schema_1.getQualifierMessage)({
121
121
  minimum: 1,
122
122
  maximum: 40,
@@ -125,7 +125,7 @@ describe("getQualifierMessage", () => {
125
125
  expect(actual).toBe(expected);
126
126
  });
127
127
  it("should render numeric exclusiveMinimum and maximum", () => {
128
- const expected = "**Possible values:** 1 < value 40";
128
+ const expected = "**Possible values:** `> 1` and `<= 40`";
129
129
  const actual = (0, schema_1.getQualifierMessage)({
130
130
  exclusiveMinimum: 1,
131
131
  maximum: 40,
@@ -133,7 +133,7 @@ describe("getQualifierMessage", () => {
133
133
  expect(actual).toBe(expected);
134
134
  });
135
135
  it("should render minimum and numeric exclusiveMaximum", () => {
136
- const expected = "**Possible values:** 1 value < 40";
136
+ const expected = "**Possible values:** `>= 1` and `< 40`";
137
137
  const actual = (0, schema_1.getQualifierMessage)({
138
138
  minimum: 1,
139
139
  exclusiveMaximum: 40,
@@ -141,7 +141,7 @@ describe("getQualifierMessage", () => {
141
141
  expect(actual).toBe(expected);
142
142
  });
143
143
  it("should render numeric exclusiveMinimum and boolean exclusiveMaximum", () => {
144
- const expected = "**Possible values:** 1 < value < 40";
144
+ const expected = "**Possible values:** `> 1` and `< 40`";
145
145
  const actual = (0, schema_1.getQualifierMessage)({
146
146
  exclusiveMinimum: 1,
147
147
  maximum: 40,
@@ -79,7 +79,6 @@ function createItems(openapiData, sidebarOptions) {
79
79
  unversionedId: tagId,
80
80
  title: description !== null && description !== void 0 ? description : "",
81
81
  description: description !== null && description !== void 0 ? description : "",
82
- slug: "/" + tagId,
83
82
  frontMatter: {},
84
83
  tag: {
85
84
  ...tag,
@@ -96,7 +95,6 @@ function createItems(openapiData, sidebarOptions) {
96
95
  unversionedId: infoId,
97
96
  title: openapiData.info.title,
98
97
  description: openapiData.info.description,
99
- slug: "/" + infoId,
100
98
  frontMatter: {},
101
99
  securitySchemes: (_b = openapiData.components) === null || _b === void 0 ? void 0 : _b.securitySchemes,
102
100
  info: {
@@ -144,7 +142,6 @@ function createItems(openapiData, sidebarOptions) {
144
142
  unversionedId: baseId,
145
143
  title: title,
146
144
  description: description !== null && description !== void 0 ? description : "",
147
- slug: "/" + baseId,
148
145
  frontMatter: {},
149
146
  api: {
150
147
  ...defaults,
@@ -15,6 +15,7 @@ const openapi_core_1 = require("@redocly/openapi-core");
15
15
  const chalk_1 = __importDefault(require("chalk"));
16
16
  // @ts-ignore
17
17
  const swagger2openapi_1 = require("swagger2openapi");
18
+ const OpenAPIParser_1 = require("./services/OpenAPIParser");
18
19
  function serializer(replacer, cycleReplacer) {
19
20
  var stack = [], keys = [];
20
21
  if (cycleReplacer === undefined)
@@ -24,6 +25,18 @@ function serializer(replacer, cycleReplacer) {
24
25
  return value.title ? `circular(${value.title})` : "circular()";
25
26
  };
26
27
  return function (key, value) {
28
+ // Resolve discriminator ref pointers
29
+ if ((value === null || value === void 0 ? void 0 : value.discriminator) !== undefined) {
30
+ const parser = new OpenAPIParser_1.OpenAPIParser(stack[0]);
31
+ for (let [k, v] of Object.entries(value.discriminator.mapping)) {
32
+ const discriminator = k;
33
+ if (typeof v === "string" && v.charAt(0) === "#") {
34
+ const ref = v;
35
+ const resolvedRef = parser.byRef(ref);
36
+ value.discriminator.mapping[discriminator] = resolvedRef;
37
+ }
38
+ }
39
+ }
27
40
  if (stack.length > 0) {
28
41
  // @ts-ignore
29
42
  var thisPos = stack.indexOf(this);
@@ -9,6 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
9
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const path_1 = __importDefault(require("path"));
12
13
  const clsx_1 = __importDefault(require("clsx"));
13
14
  const lodash_1 = require("lodash");
14
15
  const uniq_1 = __importDefault(require("lodash/uniq"));
@@ -98,8 +99,8 @@ function groupByTags(items, sidebarOptions, options, tags, docPath) {
98
99
  type: "generated-index",
99
100
  title: tag,
100
101
  slug: label
101
- ? "/category/" + (0, lodash_1.kebabCase)(label) + "/" + (0, lodash_1.kebabCase)(tag)
102
- : "/category/" + (0, lodash_1.kebabCase)(tag),
102
+ ? path_1.default.join("/category", basePath, (0, lodash_1.kebabCase)(label), (0, lodash_1.kebabCase)(tag))
103
+ : path_1.default.join("/category", basePath, (0, lodash_1.kebabCase)(tag)),
103
104
  };
104
105
  }
105
106
  return {
package/lib/types.d.ts CHANGED
@@ -51,7 +51,7 @@ export interface ApiMetadataBase {
51
51
  description: string;
52
52
  source: string;
53
53
  sourceDirName: string;
54
- slug: string;
54
+ slug?: string;
55
55
  permalink: string;
56
56
  sidebarPosition?: number;
57
57
  frontMatter: Record<string, unknown>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "docusaurus-plugin-openapi-docs",
3
3
  "description": "OpenAPI plugin for Docusaurus.",
4
- "version": "0.0.0-406",
4
+ "version": "0.0.0-409",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "openapi",
@@ -67,5 +67,5 @@
67
67
  "engines": {
68
68
  "node": ">=14"
69
69
  },
70
- "gitHead": "16858c4b489fc024973a4ffe42fc57ecc0c4f1a3"
70
+ "gitHead": "6cfbade709c0e80f79e91b3e4fa11dbf416490b5"
71
71
  }
@@ -99,6 +99,7 @@ function createAnyOneOf(schema: SchemaObject): any {
99
99
  }
100
100
 
101
101
  function createProperties(schema: SchemaObject) {
102
+ const discriminator = schema.discriminator;
102
103
  return Object.entries(schema.properties!).map(([key, val]) =>
103
104
  createEdges({
104
105
  name: key,
@@ -106,6 +107,7 @@ function createProperties(schema: SchemaObject) {
106
107
  required: Array.isArray(schema.required)
107
108
  ? schema.required.includes(key)
108
109
  : false,
110
+ discriminator,
109
111
  })
110
112
  );
111
113
  }
@@ -277,6 +279,110 @@ function createItems(schema: SchemaObject) {
277
279
  );
278
280
  }
279
281
 
282
+ /**
283
+ * For handling discriminators that do not map to a same-level property
284
+ */
285
+ function createDiscriminator(schema: SchemaObject) {
286
+ const discriminator = schema.discriminator;
287
+ const propertyName = discriminator?.propertyName;
288
+ const propertyType = "string"; // should always be string
289
+ const mapping: any = discriminator?.mapping;
290
+
291
+ // Explicit mapping is required since we can't support implicit
292
+ if (mapping === undefined) {
293
+ return undefined;
294
+ }
295
+
296
+ // Attempt to get the property description we want to display
297
+ // TODO: how to make it predictable when handling allOf
298
+ let propertyDescription;
299
+ const firstMappingSchema = mapping[Object.keys(mapping)[0]];
300
+ if (firstMappingSchema.properties !== undefined) {
301
+ propertyDescription =
302
+ firstMappingSchema.properties![propertyName!].description;
303
+ }
304
+ if (firstMappingSchema.allOf !== undefined) {
305
+ const { mergedSchemas }: { mergedSchemas: SchemaObject } = mergeAllOf(
306
+ firstMappingSchema.allOf
307
+ );
308
+ if (mergedSchemas.properties !== undefined) {
309
+ propertyDescription =
310
+ mergedSchemas.properties[propertyName!]?.description;
311
+ }
312
+ }
313
+
314
+ if (propertyDescription === undefined) {
315
+ if (
316
+ schema.properties !== undefined &&
317
+ schema.properties![propertyName!] !== undefined
318
+ ) {
319
+ propertyDescription = schema.properties![propertyName!].description;
320
+ }
321
+ }
322
+
323
+ return create("li", {
324
+ className: "discriminatorItem",
325
+ children: create("div", {
326
+ children: [
327
+ create("strong", {
328
+ style: { paddingLeft: "1rem" },
329
+ children: propertyName,
330
+ }),
331
+ guard(propertyType, (name) =>
332
+ create("span", {
333
+ style: { opacity: "0.6" },
334
+ children: ` ${propertyType}`,
335
+ })
336
+ ),
337
+ guard(getQualifierMessage(schema.discriminator as any), (message) =>
338
+ create("div", {
339
+ style: {
340
+ paddingLeft: "1rem",
341
+ },
342
+ children: createDescription(message),
343
+ })
344
+ ),
345
+ guard(propertyDescription, (description) =>
346
+ create("div", {
347
+ style: {
348
+ paddingLeft: "1rem",
349
+ },
350
+ children: createDescription(description),
351
+ })
352
+ ),
353
+ create("DiscriminatorTabs", {
354
+ children: Object.keys(mapping!).map((key, index) => {
355
+ if (mapping[key].allOf !== undefined) {
356
+ const { mergedSchemas }: { mergedSchemas: SchemaObject } =
357
+ mergeAllOf(mapping[key].allOf);
358
+ // Cleanup duplicate property from mapping schema
359
+ delete mergedSchemas.properties![propertyName!];
360
+ mapping[key] = mergedSchemas;
361
+ }
362
+
363
+ if (mapping[key].properties !== undefined) {
364
+ // Cleanup duplicate property from mapping schema
365
+ delete mapping[key].properties![propertyName!];
366
+ }
367
+
368
+ const label = key;
369
+ return create("TabItem", {
370
+ label: label,
371
+ value: `${index}-item-discriminator`,
372
+ children: [
373
+ create("div", {
374
+ style: { marginLeft: "-4px" },
375
+ children: createNodes(mapping[key]),
376
+ }),
377
+ ],
378
+ });
379
+ }),
380
+ }),
381
+ ],
382
+ }),
383
+ });
384
+ }
385
+
280
386
  function createDetailsNode(
281
387
  name: string,
282
388
  schemaName: string,
@@ -331,18 +437,109 @@ function createDetailsNode(
331
437
  });
332
438
  }
333
439
 
440
+ /**
441
+ * For handling discriminators that map to a same-level property (like 'petType').
442
+ * Note: These should only be encountered while iterating through properties.
443
+ */
444
+ function createPropertyDiscriminator(
445
+ name: string,
446
+ schemaName: string,
447
+ schema: SchemaObject,
448
+ discriminator: any,
449
+ required: any
450
+ ): any {
451
+ if (schema === undefined) {
452
+ return undefined;
453
+ }
454
+
455
+ if (discriminator.mapping === undefined) {
456
+ return undefined;
457
+ }
458
+
459
+ return create("li", {
460
+ className: "discriminatorItem",
461
+ children: create("div", {
462
+ children: [
463
+ create("strong", { style: { paddingLeft: "1rem" }, children: name }),
464
+ guard(schemaName, (name) =>
465
+ create("span", {
466
+ style: { opacity: "0.6" },
467
+ children: ` ${schemaName}`,
468
+ })
469
+ ),
470
+ guard(required, () => [
471
+ create("strong", {
472
+ style: {
473
+ fontSize: "var(--ifm-code-font-size)",
474
+ color: "var(--openapi-required)",
475
+ },
476
+ children: " required",
477
+ }),
478
+ ]),
479
+ guard(getQualifierMessage(discriminator), (message) =>
480
+ create("div", {
481
+ style: {
482
+ paddingLeft: "1rem",
483
+ },
484
+ children: createDescription(message),
485
+ })
486
+ ),
487
+ guard(schema.description, (description) =>
488
+ create("div", {
489
+ style: {
490
+ paddingLeft: "1rem",
491
+ },
492
+ children: createDescription(description),
493
+ })
494
+ ),
495
+ create("DiscriminatorTabs", {
496
+ children: Object.keys(discriminator?.mapping!).map((key, index) => {
497
+ const label = key;
498
+ return create("TabItem", {
499
+ label: label,
500
+ value: `${index}-item-discriminator`,
501
+ children: [
502
+ create("div", {
503
+ style: { marginLeft: "-4px" },
504
+ children: createNodes(discriminator?.mapping[key]),
505
+ }),
506
+ ],
507
+ });
508
+ }),
509
+ }),
510
+ ],
511
+ }),
512
+ });
513
+ }
514
+
334
515
  interface EdgeProps {
335
516
  name: string;
336
517
  schema: SchemaObject;
337
518
  required: boolean;
519
+ discriminator?: any | unknown;
338
520
  }
339
521
 
340
522
  /**
341
523
  * Creates the edges or "leaves" of a schema tree. Edges can branch into sub-nodes with createDetails().
342
524
  */
343
- function createEdges({ name, schema, required }: EdgeProps): any {
525
+ function createEdges({
526
+ name,
527
+ schema,
528
+ required,
529
+ discriminator,
530
+ }: EdgeProps): any {
344
531
  const schemaName = getSchemaName(schema);
345
532
 
533
+ if (discriminator !== undefined && discriminator.propertyName === name) {
534
+ return createPropertyDiscriminator(
535
+ name,
536
+ "string",
537
+ schema,
538
+ discriminator,
539
+ required
540
+ );
541
+ }
542
+
346
543
  if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
347
544
  return createDetailsNode(name, schemaName, schema, required);
348
545
  }
@@ -409,6 +606,10 @@ function createEdges({ name, schema, required }: EdgeProps): any {
409
606
  * Creates a hierarchical level of a schema tree. Nodes produce edges that can branch into sub-nodes with edges, recursively.
410
607
  */
411
608
  function createNodes(schema: SchemaObject): any {
609
+ if (schema.discriminator !== undefined) {
610
+ return createDiscriminator(schema);
611
+ }
612
+
412
613
  if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
413
614
  return createAnyOneOf(schema);
414
615
  }
@@ -41,6 +41,7 @@ export function createApiPageMD({
41
41
  `import SchemaItem from "@theme/SchemaItem"\n`,
42
42
  `import ApiTabs from "@theme/ApiTabs";\n`,
43
43
  `import SchemaTabs from "@theme/SchemaTabs";\n`,
44
+ `import DiscriminatorTabs from "@theme/DiscriminatorTabs";\n`,
44
45
  `import TabItem from "@theme/TabItem";\n\n`,
45
46
  `## ${escape(title)}\n\n`,
46
47
  createDeprecationNotice({ deprecated, description: deprecatedDescription }),
@@ -17,19 +17,19 @@ describe("getQualifierMessage", () => {
17
17
  // minLength + maxLength
18
18
  //
19
19
  it("should render minLength", () => {
20
- const expected = "**Possible values:** 1 ≤ length";
20
+ const expected = "**Possible values:** `non-empty`";
21
21
  const actual = getQualifierMessage({ minLength: 1 });
22
22
  expect(actual).toBe(expected);
23
23
  });
24
24
 
25
25
  it("should render maxLength", () => {
26
- const expected = "**Possible values:** length 40";
26
+ const expected = "**Possible values:** `<= 40 characters`";
27
27
  const actual = getQualifierMessage({ maxLength: 40 });
28
28
  expect(actual).toBe(expected);
29
29
  });
30
30
 
31
31
  it("should render minLength and maxLength", () => {
32
- const expected = "**Possible values:** 1 length 40";
32
+ const expected = "**Possible values:** `non-empty` and `<= 40 characters`";
33
33
  const actual = getQualifierMessage({ minLength: 1, maxLength: 40 });
34
34
  expect(actual).toBe(expected);
35
35
  });
@@ -46,7 +46,7 @@ describe("getQualifierMessage", () => {
46
46
 
47
47
  it("should render multiple string qualifiers", () => {
48
48
  const expected =
49
- "**Possible values:** 1 length 40, Value must match regular expression `^[a-zA-Z0-9_-]*$`";
49
+ "**Possible values:** `non-empty` and `<= 40 characters`, Value must match regular expression `^[a-zA-Z0-9_-]*$`";
50
50
  const actual = getQualifierMessage({
51
51
  minLength: 1,
52
52
  maxLength: 40,
@@ -68,49 +68,49 @@ describe("getQualifierMessage", () => {
68
68
  // minimum + maximum + exclusiveMinimum + exclusiveMaximum
69
69
  //
70
70
  it("should render minimum", () => {
71
- const expected = "**Possible values:** 1 ≤ value";
71
+ const expected = "**Possible values:** `>= 1`";
72
72
  const actual = getQualifierMessage({ minimum: 1 });
73
73
  expect(actual).toBe(expected);
74
74
  });
75
75
 
76
76
  it("should render maximum", () => {
77
- const expected = "**Possible values:** value 40";
77
+ const expected = "**Possible values:** `<= 40`";
78
78
  const actual = getQualifierMessage({ maximum: 40 });
79
79
  expect(actual).toBe(expected);
80
80
  });
81
81
 
82
82
  it("should render numeric exclusiveMinimum", () => {
83
- const expected = "**Possible values:** 1 < value";
83
+ const expected = "**Possible values:** `> 1`";
84
84
  const actual = getQualifierMessage({ exclusiveMinimum: 1 });
85
85
  expect(actual).toBe(expected);
86
86
  });
87
87
 
88
88
  it("should render numeric exclusiveMaximum", () => {
89
- const expected = "**Possible values:** value < 40";
89
+ const expected = "**Possible values:** `< 40`";
90
90
  const actual = getQualifierMessage({ exclusiveMaximum: 40 });
91
91
  expect(actual).toBe(expected);
92
92
  });
93
93
 
94
94
  it("should render boolean exclusiveMinimum", () => {
95
- const expected = "**Possible values:** 1 < value";
95
+ const expected = "**Possible values:** `> 1`";
96
96
  const actual = getQualifierMessage({ minimum: 1, exclusiveMinimum: true });
97
97
  expect(actual).toBe(expected);
98
98
  });
99
99
 
100
100
  it("should render boolean exclusiveMaximum", () => {
101
- const expected = "**Possible values:** value < 40";
101
+ const expected = "**Possible values:** `< 40`";
102
102
  const actual = getQualifierMessage({ maximum: 40, exclusiveMaximum: true });
103
103
  expect(actual).toBe(expected);
104
104
  });
105
105
 
106
106
  it("should render minimum when exclusiveMinimum is false", () => {
107
- const expected = "**Possible values:** 1 ≤ value";
107
+ const expected = "**Possible values:** `>= 1`";
108
108
  const actual = getQualifierMessage({ minimum: 1, exclusiveMinimum: false });
109
109
  expect(actual).toBe(expected);
110
110
  });
111
111
 
112
112
  it("should render maximum when exclusiveMaximum is false", () => {
113
- const expected = "**Possible values:** value 40";
113
+ const expected = "**Possible values:** `<= 40`";
114
114
  const actual = getQualifierMessage({
115
115
  maximum: 40,
116
116
  exclusiveMaximum: false,
@@ -119,13 +119,13 @@ describe("getQualifierMessage", () => {
119
119
  });
120
120
 
121
121
  it("should render minimum and maximum", () => {
122
- const expected = "**Possible values:** 1 value 40";
122
+ const expected = "**Possible values:** `>= 1` and `<= 40`";
123
123
  const actual = getQualifierMessage({ minimum: 1, maximum: 40 });
124
124
  expect(actual).toBe(expected);
125
125
  });
126
126
 
127
127
  it("should render boolean exclusiveMinimum and maximum", () => {
128
- const expected = "**Possible values:** 1 < value 40";
128
+ const expected = "**Possible values:** `> 1` and `<= 40`";
129
129
  const actual = getQualifierMessage({
130
130
  minimum: 1,
131
131
  maximum: 40,
@@ -135,7 +135,7 @@ describe("getQualifierMessage", () => {
135
135
  });
136
136
 
137
137
  it("should render minimum and boolean exclusiveMaximum", () => {
138
- const expected = "**Possible values:** 1 value < 40";
138
+ const expected = "**Possible values:** `>= 1` and `< 40`";
139
139
  const actual = getQualifierMessage({
140
140
  minimum: 1,
141
141
  maximum: 40,
@@ -145,7 +145,7 @@ describe("getQualifierMessage", () => {
145
145
  });
146
146
 
147
147
  it("should render numeric exclusiveMinimum and maximum", () => {
148
- const expected = "**Possible values:** 1 < value 40";
148
+ const expected = "**Possible values:** `> 1` and `<= 40`";
149
149
  const actual = getQualifierMessage({
150
150
  exclusiveMinimum: 1,
151
151
  maximum: 40,
@@ -154,7 +154,7 @@ describe("getQualifierMessage", () => {
154
154
  });
155
155
 
156
156
  it("should render minimum and numeric exclusiveMaximum", () => {
157
- const expected = "**Possible values:** 1 value < 40";
157
+ const expected = "**Possible values:** `>= 1` and `< 40`";
158
158
  const actual = getQualifierMessage({
159
159
  minimum: 1,
160
160
  exclusiveMaximum: 40,
@@ -163,7 +163,7 @@ describe("getQualifierMessage", () => {
163
163
  });
164
164
 
165
165
  it("should render numeric exclusiveMinimum and boolean exclusiveMaximum", () => {
166
- const expected = "**Possible values:** 1 < value < 40";
166
+ const expected = "**Possible values:** `> 1` and `< 40`";
167
167
  const actual = getQualifierMessage({
168
168
  exclusiveMinimum: 1,
169
169
  maximum: 40,
@@ -74,13 +74,28 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
74
74
 
75
75
  if (schema.minLength || schema.maxLength) {
76
76
  let lengthQualifier = "";
77
- if (schema.minLength) {
78
- lengthQualifier += `${schema.minLength} ≤ `;
77
+ let minLength;
78
+ let maxLength;
79
+ if (schema.minLength && schema.minLength > 1) {
80
+ minLength = `\`>= ${schema.minLength} characters\``;
81
+ }
82
+ if (schema.minLength && schema.minLength === 1) {
83
+ minLength = `\`non-empty\``;
79
84
  }
80
- lengthQualifier += "length";
81
85
  if (schema.maxLength) {
82
- lengthQualifier += ` ${schema.maxLength}`;
86
+ maxLength = `\`<= ${schema.maxLength} characters\``;
87
+ }
88
+
89
+ if (minLength && !maxLength) {
90
+ lengthQualifier += minLength;
91
+ }
92
+ if (maxLength && !minLength) {
93
+ lengthQualifier += maxLength;
83
94
  }
95
+ if (minLength && maxLength) {
96
+ lengthQualifier += `${minLength} and ${maxLength}`;
97
+ }
98
+
84
99
  qualifierGroups.push(lengthQualifier);
85
100
  }
86
101
 
@@ -91,21 +106,33 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
91
106
  typeof schema.exclusiveMaximum === "number"
92
107
  ) {
93
108
  let minmaxQualifier = "";
109
+ let minimum;
110
+ let maximum;
94
111
  if (typeof schema.exclusiveMinimum === "number") {
95
- minmaxQualifier += `${schema.exclusiveMinimum} < `;
112
+ minimum = `\`> ${schema.exclusiveMinimum}\``;
96
113
  } else if (schema.minimum && !schema.exclusiveMinimum) {
97
- minmaxQualifier += `${schema.minimum} ≤ `;
114
+ minimum = `\`>= ${schema.minimum}\``;
98
115
  } else if (schema.minimum && schema.exclusiveMinimum === true) {
99
- minmaxQualifier += `${schema.minimum} < `;
116
+ minimum = `\`> ${schema.minimum}\``;
100
117
  }
101
- minmaxQualifier += "value";
102
118
  if (typeof schema.exclusiveMaximum === "number") {
103
- minmaxQualifier += ` < ${schema.exclusiveMaximum}`;
119
+ maximum = `\`< ${schema.exclusiveMaximum}\``;
104
120
  } else if (schema.maximum && !schema.exclusiveMaximum) {
105
- minmaxQualifier += ` ${schema.maximum}`;
121
+ maximum = `\`<= ${schema.maximum}\``;
106
122
  } else if (schema.maximum && schema.exclusiveMaximum === true) {
107
- minmaxQualifier += ` < ${schema.maximum}`;
123
+ maximum = `\`< ${schema.maximum}\``;
108
124
  }
125
+
126
+ if (minimum && !maximum) {
127
+ minmaxQualifier += minimum;
128
+ }
129
+ if (maximum && !minimum) {
130
+ minmaxQualifier += maximum;
131
+ }
132
+ if (minimum && maximum) {
133
+ minmaxQualifier += `${minimum} and ${maximum}`;
134
+ }
135
+
109
136
  qualifierGroups.push(minmaxQualifier);
110
137
  }
111
138
 
@@ -115,16 +142,23 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
115
142
  );
116
143
  }
117
144
 
145
+ // Check if discriminator mapping
146
+ const discriminator = schema as any;
147
+ if (discriminator.mapping) {
148
+ const values = Object.keys(discriminator.mapping);
149
+ qualifierGroups.push(`[${values.map((e) => `\`${e}\``).join(", ")}]`);
150
+ }
151
+
118
152
  if (schema.enum) {
119
153
  qualifierGroups.push(`[${schema.enum.map((e) => `\`${e}\``).join(", ")}]`);
120
154
  }
121
155
 
122
156
  if (schema.minItems) {
123
- qualifierGroups.push(`items >= ${schema.minItems}`);
157
+ qualifierGroups.push(`\`>= ${schema.minItems}\``);
124
158
  }
125
159
 
126
160
  if (schema.maxItems) {
127
- qualifierGroups.push(`items <= ${schema.maxItems}`);
161
+ qualifierGroups.push(`\`<= ${schema.maxItems}\``);
128
162
  }
129
163
 
130
164
  if (qualifierGroups.length === 0) {
@@ -102,7 +102,6 @@ function createItems(
102
102
  unversionedId: tagId,
103
103
  title: description ?? "",
104
104
  description: description ?? "",
105
- slug: "/" + tagId,
106
105
  frontMatter: {},
107
106
  tag: {
108
107
  ...tag,
@@ -120,7 +119,6 @@ function createItems(
120
119
  unversionedId: infoId,
121
120
  title: openapiData.info.title,
122
121
  description: openapiData.info.description,
123
- slug: "/" + infoId,
124
122
  frontMatter: {},
125
123
  securitySchemes: openapiData.components?.securitySchemes,
126
124
  info: {
@@ -184,7 +182,6 @@ function createItems(
184
182
  unversionedId: baseId,
185
183
  title: title,
186
184
  description: description ?? "",
187
- slug: "/" + baseId,
188
185
  frontMatter: {},
189
186
  api: {
190
187
  ...defaults,
@@ -14,6 +14,7 @@ import chalk from "chalk";
14
14
  import { convertObj } from "swagger2openapi";
15
15
 
16
16
  import { OpenApiObject } from "../types";
17
+ import { OpenAPIParser } from "./services/OpenAPIParser";
17
18
 
18
19
  function serializer(replacer: any, cycleReplacer: any) {
19
20
  var stack: any = [],
@@ -26,6 +27,18 @@ function serializer(replacer: any, cycleReplacer: any) {
26
27
  };
27
28
 
28
29
  return function (key: any, value: any) {
30
+ // Resolve discriminator ref pointers
31
+ if (value?.discriminator !== undefined) {
32
+ const parser = new OpenAPIParser(stack[0]);
33
+ for (let [k, v] of Object.entries(value.discriminator.mapping)) {
34
+ const discriminator = k as string;
35
+ if (typeof v === "string" && v.charAt(0) === "#") {
36
+ const ref = v as string;
37
+ const resolvedRef = parser.byRef(ref);
38
+ value.discriminator.mapping[discriminator] = resolvedRef;
39
+ }
40
+ }
41
+ }
29
42
  if (stack.length > 0) {
30
43
  // @ts-ignore
31
44
  var thisPos = stack.indexOf(this);
@@ -5,6 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  * ========================================================================== */
7
7
 
8
+ import path from "path";
9
+
8
10
  import {
9
11
  ProcessedSidebar,
10
12
  SidebarItemCategory,
@@ -139,8 +141,8 @@ function groupByTags(
139
141
  type: "generated-index" as "generated-index",
140
142
  title: tag,
141
143
  slug: label
142
- ? "/category/" + kebabCase(label) + "/" + kebabCase(tag)
143
- : "/category/" + kebabCase(tag),
144
+ ? path.join("/category", basePath, kebabCase(label), kebabCase(tag))
145
+ : path.join("/category", basePath, kebabCase(tag)),
144
146
  } as SidebarItemCategoryLinkConfig;
145
147
  }
146
148
 
package/src/types.ts CHANGED
@@ -76,7 +76,7 @@ export interface ApiMetadataBase {
76
76
  description: string;
77
77
  source: string; // @site aliased source => "@site/docs/folder/subFolder/subSubFolder/myDoc.md"
78
78
  sourceDirName: string; // relative to the versioned docs folder (can be ".") => "folder/subFolder/subSubFolder"
79
- slug: string;
79
+ slug?: string;
80
80
  permalink: string;
81
81
  sidebarPosition?: number;
82
82
  frontMatter: Record<string, unknown>;