Skip to content

Commit

Permalink
feat: Implement schema pages behind a config option showSchemas (#736)
Browse files Browse the repository at this point in the history
* feat: Implement schema pages behind a config option `showSchemas`

* Add `showSchemas` to petstore demo
  • Loading branch information
MarcL01 committed Mar 12, 2024
1 parent cc5067d commit 026074f
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ dist
demo/**/*.api.mdx
demo/**/*.info.mdx
demo/**/*.tag.mdx
demo/**/*.schema.mdx
demo/**/sidebar.js
demo/**/versions.json

12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
| `baseUrl` | `string` | `null` | _Optional:_ Version base URL used when generating version selector dropdown menu. |
| `versions` | `object` | `null` | _Optional:_ Set of options for versioning configuration. See below for a list of supported options. |
| `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content with a set of options for specifying markdown generator functions. See below for a list of supported options. |
| `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates schema pages and adds them to the sidebar. |

`sidebarOptions` can be configured with the following options:

Expand Down Expand Up @@ -197,11 +198,12 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following

`markdownGenerators` can be configured with the following options:

| Name | Type | Default | Description |
| ------------------ | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `createApiPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for API pages.<br/><br/>**Function type:** `(pageData: ApiPageMetadata) => string` |
| `createInfoPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for info pages.<br/><br/>**Function type:** `(pageData: InfoPageMetadata) => string` |
| `createTagPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for tag pages.<br/><br/>**Function type:** `(pageData: TagPageMetadata) => string` |
| Name | Type | Default | Description |
| -------------------- | ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `createApiPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for API pages.<br/><br/>**Function type:** `(pageData: ApiPageMetadata) => string` |
| `createInfoPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for info pages.<br/><br/>**Function type:** `(pageData: InfoPageMetadata) => string` |
| `createTagPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for tag pages.<br/><br/>**Function type:** `(pageData: TagPageMetadata) => string` |
| `createSchemaPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for schema pages.<br/><br/>**Function type:** `(pageData: SchemaPageMetadata) => string` |

## CLI Usage

Expand Down
12 changes: 7 additions & 5 deletions demo/docs/intro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
| `baseUrl` | `string` | `null` | _Optional:_ Version base URL used when generating version selector dropdown menu. |
| `versions` | `object` | `null` | _Optional:_ Set of options for versioning configuration. See below for a list of supported options. |
| `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content with a set of options for specifying markdown generator functions. See below for a list of supported options. |
| `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates schema pages and adds them to the sidebar. |

### sidebarOptions

Expand Down Expand Up @@ -373,11 +374,12 @@ All versions will automatically inherit `sidebarOptions` from the parent/base co

`markdownGenerators` can be configured with the following options:

| Name | Type | Default | Description |
| ------------------ | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------|
| `createApiPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for API pages.<br/><br/>**Function type:** `(pageData: ApiPageMetadata) => string` |
| `createInfoPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for info pages.<br/><br/>**Function type:** `(pageData: InfoPageMetadata) => string` |
| `createTagPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for tag pages.<br/><br/>**Function type:** `(pageData: TagPageMetadata) => string` |
| Name | Type | Default | Description |
| ------------------- | ---------- | ------- | --------------------------------------------------------------------------------------------------------------------------------|
| `createApiPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for API pages.<br/><br/>**Function type:** `(pageData: ApiPageMetadata) => string` |
| `createInfoPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for info pages.<br/><br/>**Function type:** `(pageData: InfoPageMetadata) => string` |
| `createTagPageMD` | `function` | `null` | _Optional:_ Returns a string of the raw markdown body for tag pages.<br/><br/>**Function type:** `(pageData: TagPageMetadata) => string` |
| `createSchemaPageMD`| `function` | `null` | _Optional:_ Returns a string of the raw markdown body for schema pages.<br/><br/>**Function type:** `(pageData: SchemaPageMetadata) => string` |

:::info NOTE
If a config option is not provided, its default markdown generator will be used.
Expand Down
1 change: 1 addition & 0 deletions demo/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ const config = {
downloadUrl:
"https://raw.githubusercontent.com/PaloAltoNetworks/docusaurus-openapi-docs/main/demo/examples/petstore.yaml",
hideSendButton: false,
showSchemas: true,
},
cos: {
specPath: "examples/openapi-cos.json",
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-openapi-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
| `baseUrl` | `string` | `null` | _Optional:_ Version base URL used when generating version selector dropdown menu. |
| `versions` | `object` | `null` | _Optional:_ Set of options for versioning configuration. See below for a list of supported options. |
| `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content with a set of options for specifying markdown generator functions. See below for a list of supported options. |
| `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates schema pages and adds them to the sidebar. |

`sidebarOptions` can be configured with the following options:

Expand Down
118 changes: 110 additions & 8 deletions packages/docusaurus-plugin-openapi-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,25 @@ import { Globby, posixPath } from "@docusaurus/utils";
import chalk from "chalk";
import { render } from "mustache";

import { createApiPageMD, createInfoPageMD, createTagPageMD } from "./markdown";
import {
createApiPageMD,
createInfoPageMD,
createSchemaPageMD,
createTagPageMD,
} from "./markdown";
import { readOpenapiFiles, processOpenapiFiles } from "./openapi";
import { OptionsSchema } from "./options";
import generateSidebarSlice from "./sidebars";
import type { PluginOptions, LoadedContent, APIOptions } from "./types";
import type {
PluginOptions,
LoadedContent,
APIOptions,
ApiMetadata,
ApiPageMetadata,
InfoPageMetadata,
TagPageMetadata,
SchemaPageMetadata,
} from "./types";

export function isURL(str: string): boolean {
return /^(https?:)\/\//m.test(str);
Expand Down Expand Up @@ -244,25 +258,51 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
\`\`\`
`;

const schemaMdTemplate = `---
id: {{{id}}}
title: "{{{title}}}"
description: "{{{frontMatter.description}}}"
sidebar_label: "{{{title}}}"
hide_title: true
schema: true
custom_edit_url: null
---
{{{markdown}}}
`;

const apiPageGenerator =
markdownGenerators?.createApiPageMD ?? createApiPageMD;
const infoPageGenerator =
markdownGenerators?.createInfoPageMD ?? createInfoPageMD;
const tagPageGenerator =
markdownGenerators?.createTagPageMD ?? createTagPageMD;
const schemaPageGenerator =
markdownGenerators?.createSchemaPageMD ?? createSchemaPageMD;

const pageGeneratorByType: {
[key in ApiMetadata["type"]]: (
pageData: {
api: ApiPageMetadata;
info: InfoPageMetadata;
tag: TagPageMetadata;
schema: SchemaPageMetadata;
}[key]
) => string;
} = {
api: apiPageGenerator,
info: infoPageGenerator,
tag: tagPageGenerator,
schema: schemaPageGenerator,
};

loadedApi.map(async (item) => {
if (item.type === "info") {
if (downloadUrl && isURL(downloadUrl)) {
item.downloadUrl = downloadUrl;
}
}
const markdown =
item.type === "api"
? apiPageGenerator(item)
: item.type === "info"
? infoPageGenerator(item)
: tagPageGenerator(item);
const markdown = pageGeneratorByType[item.type](item as any);
item.markdown = markdown;
if (item.type === "api") {
// opportunity to compress JSON
Expand Down Expand Up @@ -363,6 +403,49 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
}
}
}

if (item.type === "schema") {
if (!fs.existsSync(`${outputDir}/schemas/${item.id}.schema.mdx`)) {
if (!fs.existsSync(`${outputDir}/schemas`)) {
try {
fs.mkdirSync(`${outputDir}/schemas`, { recursive: true });
console.log(
chalk.green(`Successfully created "${outputDir}/schemas"`)
);
} catch (err) {
console.error(
chalk.red(`Failed to create "${outputDir}/schemas"`),
chalk.yellow(err)
);
}
}
try {
// kebabCase(arg) returns 0-length string when arg is undefined
if (item.id.length === 0) {
throw Error("Schema must have title defined");
}
// eslint-disable-next-line testing-library/render-result-naming-convention
const schemaView = render(schemaMdTemplate, item);
fs.writeFileSync(
`${outputDir}/schemas/${item.id}.schema.mdx`,
schemaView,
"utf8"
);
console.log(
chalk.green(
`Successfully created "${outputDir}/${item.id}.schema.mdx"`
)
);
} catch (err) {
console.error(
chalk.red(
`Failed to write "${outputDir}/${item.id}.schema.mdx"`
),
chalk.yellow(err)
);
}
}
}
return;
});

Expand All @@ -380,6 +463,10 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
cwd: path.resolve(apiDir),
deep: 1,
});
const schemaMdxFiles = await Globby(["*.schema.mdx"], {
cwd: path.resolve(apiDir, "schemas"),
deep: 1,
});
const sidebarFile = await Globby(["sidebar.js"], {
cwd: path.resolve(apiDir),
deep: 1,
Expand All @@ -397,6 +484,21 @@ import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
})
);

schemaMdxFiles.map((mdx) =>
fs.unlink(`${apiDir}/schemas/${mdx}`, (err) => {
if (err) {
console.error(
chalk.red(`Cleanup failed for "${apiDir}/schemas/${mdx}"`),
chalk.yellow(err)
);
} else {
console.log(
chalk.green(`Cleanup succeeded for "${apiDir}/schemas/${mdx}"`)
);
}
})
);

sidebarFile.map((sidebar) =>
fs.unlink(`${apiDir}/${sidebar}`, (err) => {
if (err) {
Expand Down
25 changes: 23 additions & 2 deletions packages/docusaurus-plugin-openapi-docs/src/markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
MediaTypeObject,
SecuritySchemeObject,
} from "../openapi/types";
import { ApiPageMetadata, InfoPageMetadata, TagPageMetadata } from "../types";
import {
ApiPageMetadata,
InfoPageMetadata,
SchemaPageMetadata,
TagPageMetadata,
} from "../types";
import { createAuthentication } from "./createAuthentication";
import { createAuthorization } from "./createAuthorization";
import { createCallbacks } from "./createCallbacks";
Expand All @@ -26,11 +31,12 @@ import { createMethodEndpoint } from "./createMethodEndpoint";
import { createParamsDetails } from "./createParamsDetails";
import { createRequestBodyDetails } from "./createRequestBodyDetails";
import { createRequestHeader } from "./createRequestHeader";
import { createNodes } from "./createSchema";
import { createStatusCodes } from "./createStatusCodes";
import { createTermsOfService } from "./createTermsOfService";
import { createVendorExtensions } from "./createVendorExtensions";
import { createVersionBadge } from "./createVersionBadge";
import { greaterThan, lessThan, render } from "./utils";
import { create, greaterThan, lessThan, render } from "./utils";

interface RequestBodyProps {
title: string;
Expand Down Expand Up @@ -130,3 +136,18 @@ export function createInfoPageMD({
export function createTagPageMD({ tag: { description } }: TagPageMetadata) {
return render([createDescription(description)]);
}

export function createSchemaPageMD({ schema }: SchemaPageMetadata) {
const { title = "", description } = schema;
return render([
`import DiscriminatorTabs from "@theme/DiscriminatorTabs";\n`,
`import SchemaItem from "@theme/SchemaItem";\n`,
`import SchemaTabs from "@theme/SchemaTabs";\n`,
`import TabItem from "@theme/TabItem";\n\n`,
createHeading(title.replace(lessThan, "&lt;").replace(greaterThan, "&gt;")),
createDescription(description),
create("ul", {
children: createNodes(schema, "response"),
}),
]);
}
47 changes: 46 additions & 1 deletion packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
APIOptions,
ApiPageMetadata,
InfoPageMetadata,
SchemaPageMetadata,
SidebarOptions,
TagPageMetadata,
} from "../types";
Expand Down Expand Up @@ -409,6 +410,46 @@ function createItems(
}
}

if (options?.showSchemas === true) {
// Gather schemas
for (let [schema, schemaObject] of Object.entries(
openapiData?.components?.schemas ?? {}
)) {
const baseIdSpaces =
schemaObject?.title?.replace(" ", "-").toLowerCase() ?? "";
const baseId = kebabCase(baseIdSpaces);

const schemaDescription = schemaObject.description;
let splitDescription: any;
if (schemaDescription) {
splitDescription = schemaDescription.match(/[^\r\n]+/g);
}

const schemaPage: PartialPage<SchemaPageMetadata> = {
type: "schema",
id: baseId,
infoId: infoId ?? "",
unversionedId: baseId,
title: schemaObject.title
? schemaObject.title.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
: schema,
description: schemaObject.description
? schemaObject.description.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
: "",
frontMatter: {
description: splitDescription
? splitDescription[0]
.replace(/((?:^|[^\\])(?:\\{2})*)"/g, "$1'")
.replace(/\s+$/, "")
: "",
},
schema: schemaObject,
};

items.push(schemaPage);
}
}

if (sidebarOptions?.categoryLinkSource === "tag") {
// Get global tags
const tags: TagObject[] = openapiData.tags ?? [];
Expand Down Expand Up @@ -471,7 +512,11 @@ function bindCollectionToApiItems(
.getPath({ unresolved: true }) // unresolved returns "/:variableName" instead of "/<type>"
.replace(/(?<![a-z0-9-_]+):([a-z0-9-_]+)/gi, "{$1}"); // replace "/:variableName" with "/{variableName}"
const apiItem = items.find((item) => {
if (item.type === "info" || item.type === "tag") {
if (
item.type === "info" ||
item.type === "tag" ||
item.type === "schema"
) {
return false;
}
return item.api.path === path && item.api.method === method;
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-openapi-docs/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const OptionsSchema = Joi.object({
showExtensions: Joi.boolean(),
sidebarOptions: sidebarOptions,
markdownGenerators: markdownGenerators,
showSchemas: Joi.boolean(),
version: Joi.string().when("versions", {
is: Joi.exist(),
then: Joi.required(),
Expand Down
Loading

0 comments on commit 026074f

Please sign in to comment.