diff --git a/demo/docusaurus.config.ts b/demo/docusaurus.config.ts
index 0c051fc29..ed511aece 100644
--- a/demo/docusaurus.config.ts
+++ b/demo/docusaurus.config.ts
@@ -71,6 +71,10 @@ const config: Config = {
label: "Petstore (versioned)",
to: "/category/petstore-versioned-api",
},
+ {
+ label: "Tests",
+ to: "/category/tests",
+ },
],
},
{
@@ -268,6 +272,16 @@ const config: Config = {
},
showSchemas: true,
} satisfies OpenApiPlugin.Options,
+ tests: {
+ specPath: "examples/tests",
+ outputDir: "docs/tests",
+ sidebarOptions: {
+ groupPathsBy: "tag",
+ categoryLinkSource: "info",
+ },
+ hideSendButton: true,
+ showSchemas: true,
+ } satisfies OpenApiPlugin.Options,
} satisfies Plugin.PluginOptions,
},
],
diff --git a/demo/examples/tests/allOf.yaml b/demo/examples/tests/allOf.yaml
new file mode 100644
index 000000000..6d5dd9ae6
--- /dev/null
+++ b/demo/examples/tests/allOf.yaml
@@ -0,0 +1,282 @@
+openapi: 3.0.1
+info:
+ title: AllOf Variations API
+ description: Demonstrates various allOf schema combinations.
+ version: 1.0.0
+tags:
+ - name: allOf
+ description: allOf tests
+paths:
+ /multiple-allof-nested:
+ get:
+ tags:
+ - allOf
+ summary: Multiple allOf with Nested Properties
+ description: |
+ Schema:
+ ```yaml
+ allOf:
+ - type: object
+ properties:
+ outerProp1:
+ type: object
+ properties:
+ innerProp1:
+ type: string
+ - type: object
+ properties:
+ outerProp2:
+ type: object
+ properties:
+ innerProp2:
+ type: number
+ ```
+ responses:
+ "200":
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ outerProp1:
+ type: object
+ properties:
+ innerProp1:
+ type: string
+ - type: object
+ properties:
+ outerProp2:
+ type: object
+ properties:
+ innerProp2:
+ type: number
+
+ /allof-shared-required:
+ get:
+ tags:
+ - allOf
+ summary: allOf with Shared Required Properties
+ description: |
+ Schema:
+ ```yaml
+ allOf:
+ - type: object
+ properties:
+ sharedProp:
+ type: string
+ required: [sharedProp]
+ - type: object
+ properties:
+ anotherProp:
+ type: number
+ required: [anotherProp]
+ ```
+ responses:
+ "200":
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ sharedProp:
+ type: string
+ required: [sharedProp]
+ - type: object
+ properties:
+ anotherProp:
+ type: number
+ required: [anotherProp]
+
+ # /allof-conflicting-properties:
+ # get:
+ # tags:
+ # - allOf
+ # summary: allOf with Conflicting Properties
+ # description: |
+ # Schema:
+ # ```yaml
+ # allOf:
+ # - type: object
+ # properties:
+ # conflictingProp:
+ # type: string
+ # - type: object
+ # properties:
+ # conflictingProp:
+ # type: number
+ # ```
+ # responses:
+ # '200':
+ # description: Successful response
+ # content:
+ # application/json:
+ # schema:
+ # allOf:
+ # - type: object
+ # properties:
+ # conflictingProp:
+ # type: string
+ # - type: object
+ # properties:
+ # conflictingProp:
+ # type: number
+
+ # /allof-mixed-data-types:
+ # get:
+ # tags:
+ # - allOf
+ # summary: allOf with Mixed Data Types
+ # description: |
+ # Schema:
+ # ```yaml
+ # allOf:
+ # - type: object
+ # properties:
+ # mixedTypeProp1:
+ # type: string
+ # - type: array
+ # items:
+ # type: number
+ # ```
+ # responses:
+ # '200':
+ # description: Successful response
+ # content:
+ # application/json:
+ # schema:
+ # allOf:
+ # - type: object
+ # properties:
+ # mixedTypeProp1:
+ # type: string
+ # - type: array
+ # items:
+ # type: number
+
+ /allof-deep-merging:
+ get:
+ tags:
+ - allOf
+ summary: allOf with Deep Merging
+ description: |
+ Schema:
+ ```yaml
+ allOf:
+ - type: object
+ properties:
+ deepProp:
+ type: object
+ properties:
+ innerProp1:
+ type: string
+ - type: object
+ properties:
+ deepProp:
+ type: object
+ properties:
+ innerProp2:
+ type: number
+ ```
+ responses:
+ "200":
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ deepProp:
+ type: object
+ properties:
+ innerProp1:
+ type: string
+ - type: object
+ properties:
+ deepProp:
+ type: object
+ properties:
+ innerProp2:
+ type: number
+
+ # /allof-discriminator:
+ # get:
+ # tags:
+ # - allOf
+ # summary: allOf with Discriminator
+ # description: |
+ # Schema:
+ # ```yaml
+ # allOf:
+ # - type: object
+ # discriminator:
+ # propertyName: type
+ # properties:
+ # type:
+ # type: string
+ # - type: object
+ # properties:
+ # specificProp:
+ # type: string
+ # ```
+ # responses:
+ # "200":
+ # description: Successful response
+ # content:
+ # application/json:
+ # schema:
+ # allOf:
+ # - type: object
+ # discriminator:
+ # propertyName: type
+ # properties:
+ # type:
+ # type: string
+ # - type: object
+ # properties:
+ # specificProp:
+ # type: string
+
+ /allof-same-level-properties:
+ get:
+ tags:
+ - allOf
+ summary: allOf with Same-Level Properties
+ description: |
+ Schema:
+ ```yaml
+ allOf:
+ - type: object
+ properties:
+ allOfProp1:
+ type: string
+ allOfProp2:
+ type: string
+ properties:
+ parentProp1:
+ type: string
+ parentProp2:
+ type: string
+ ```
+ responses:
+ "200":
+ description: Successful response
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ allOfProp1:
+ type: string
+ allOfProp2:
+ type: string
+ properties:
+ parentProp1:
+ type: string
+ parentProp2:
+ type: string
diff --git a/demo/sidebars.ts b/demo/sidebars.ts
index cc4e4255b..037e3c4cc 100644
--- a/demo/sidebars.ts
+++ b/demo/sidebars.ts
@@ -170,6 +170,20 @@ const sidebars: SidebarsConfig = {
items: petstoreVersionSidebar,
},
],
+
+ tests: [
+ {
+ type: "category",
+ label: "Tests",
+ link: {
+ type: "generated-index",
+ title: "Tests",
+ description: "Various OpenAPI test cases",
+ slug: "/category/tests",
+ },
+ items: require("./docs/tests/sidebar.js"),
+ },
+ ],
};
export default sidebars;
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap b/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
index 566438c29..82bff053d 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/__snapshots__/createSchema.test.ts.snap
@@ -223,7 +223,152 @@ Array [
]
`;
-exports[`createNodes should create readable MODs for oneOf primitive properties 1`] = `
+exports[`createNodes allOf should correctly deep merge properties in allOf schemas 1`] = `
+Array [
+ "
+
+
+
+ deepProp
+ object
+
+
+
+
+
+
+
+;
+",
+]
+`;
+
+exports[`createNodes allOf should correctly handle shared required properties across allOf schemas 1`] = `
+Array [
+ ";
+",
+ ";
+",
+]
+`;
+
+exports[`createNodes allOf should correctly merge nested properties from multiple allOf schemas 1`] = `
+Array [
+ "
+
+
+
+ outerProp1
+ object
+
+
+
+
+
+
+;
+",
+ "
+
+
+
+ outerProp2
+ object
+
+
+
+
+
+
+;
+",
+]
+`;
+
+exports[`createNodes allOf should render same-level properties with allOf 1`] = `
+Array [
+ ";
+",
+ ";
+",
+ ";
+",
+ ";
+",
+]
+`;
+
+exports[`createNodes oneOf should create readable MODs for oneOf primitive properties 1`] = `
Array [
"
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.test.ts b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.test.ts
index 978fcbf4e..a9a4f964a 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.test.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.test.ts
@@ -11,50 +11,306 @@ import { createNodes } from "./createSchema";
import { SchemaObject } from "../openapi/types";
describe("createNodes", () => {
- it("should create readable MODs for oneOf primitive properties", async () => {
- const schema: SchemaObject = {
- "x-tags": ["clown"],
- type: "object",
- properties: {
- oneOfProperty: {
- oneOf: [
- {
- type: "object",
- properties: {
- noseLength: {
- type: "number",
+ describe("oneOf", () => {
+ it("should create readable MODs for oneOf primitive properties", async () => {
+ const schema: SchemaObject = {
+ "x-tags": ["clown"],
+ type: "object",
+ properties: {
+ oneOfProperty: {
+ oneOf: [
+ {
+ type: "object",
+ properties: {
+ noseLength: {
+ type: "number",
+ },
},
+ required: ["noseLength"],
+ description: "Clown's nose length",
+ },
+ {
+ type: "array",
+ items: {
+ type: "string",
+ },
+ description: "Array of strings",
+ },
+ {
+ type: "boolean",
+ },
+ {
+ type: "number",
+ },
+ {
+ type: "string",
+ },
+ ],
+ },
+ },
+ };
+ expect(
+ await Promise.all(
+ createNodes(schema, "request").map(
+ async (md: any) => await prettier.format(md, { parser: "babel" })
+ )
+ )
+ ).toMatchSnapshot();
+ });
+ });
+
+ describe("allOf", () => {
+ it("should render same-level properties with allOf", async () => {
+ const schema: SchemaObject = {
+ allOf: [
+ {
+ type: "object",
+ properties: {
+ allOfProp1: {
+ type: "string",
+ },
+ allOfProp2: {
+ type: "string",
},
- required: ["noseLength"],
- description: "Clown's nose length",
},
- {
- type: "array",
- items: {
+ },
+ ],
+ properties: {
+ parentProp1: {
+ type: "string",
+ },
+ parentProp2: {
+ type: "string",
+ },
+ },
+ };
+
+ expect(
+ await Promise.all(
+ createNodes(schema, "response").map(
+ async (md: any) => await prettier.format(md, { parser: "babel" })
+ )
+ )
+ ).toMatchSnapshot();
+ });
+
+ it("should correctly merge nested properties from multiple allOf schemas", async () => {
+ const schema: SchemaObject = {
+ allOf: [
+ {
+ type: "object",
+ properties: {
+ outerProp1: {
+ type: "object",
+ properties: {
+ innerProp1: {
+ type: "string",
+ },
+ },
+ },
+ },
+ },
+ {
+ type: "object",
+ properties: {
+ outerProp2: {
+ type: "object",
+ properties: {
+ innerProp2: {
+ type: "number",
+ },
+ },
+ },
+ },
+ },
+ ],
+ };
+
+ expect(
+ await Promise.all(
+ createNodes(schema, "response").map(
+ async (md: any) => await prettier.format(md, { parser: "babel" })
+ )
+ )
+ ).toMatchSnapshot();
+ });
+
+ it("should correctly handle shared required properties across allOf schemas", async () => {
+ const schema: SchemaObject = {
+ allOf: [
+ {
+ type: "object",
+ properties: {
+ sharedProp: {
type: "string",
},
- description: "Array of strings",
},
- {
- type: "boolean",
+ required: ["sharedProp"],
+ },
+ {
+ type: "object",
+ properties: {
+ anotherProp: {
+ type: "number",
+ },
},
- {
- type: "number",
+ required: ["anotherProp"],
+ },
+ ],
+ };
+
+ expect(
+ await Promise.all(
+ createNodes(schema, "response").map(
+ async (md: any) => await prettier.format(md, { parser: "babel" })
+ )
+ )
+ ).toMatchSnapshot();
+ });
+
+ // Could not resolve values for path:"properties.conflictingProp.type". They are probably incompatible. Values:
+ // "string"
+ // "number"
+ // eslint-disable-next-line jest/no-commented-out-tests
+ // it("should handle conflicting properties in allOf schemas", async () => {
+ // const schema: SchemaObject = {
+ // allOf: [
+ // {
+ // type: "object",
+ // properties: {
+ // conflictingProp: {
+ // type: "string",
+ // },
+ // },
+ // },
+ // {
+ // type: "object",
+ // properties: {
+ // conflictingProp: {
+ // type: "number",
+ // },
+ // },
+ // },
+ // ],
+ // };
+
+ // expect(
+ // await Promise.all(
+ // createNodes(schema, "response").map(
+ // async (md: any) => await prettier.format(md, { parser: "babel" })
+ // )
+ // )
+ // ).toMatchSnapshot();
+ // });
+
+ // Could not resolve values for path:"type". They are probably incompatible. Values:
+ // "object"
+ // "array"
+ // eslint-disable-next-line jest/no-commented-out-tests
+ // it("should handle mixed data types in allOf schemas", async () => {
+ // const schema: SchemaObject = {
+ // allOf: [
+ // {
+ // type: "object",
+ // properties: {
+ // mixedTypeProp1: {
+ // type: "string",
+ // },
+ // },
+ // },
+ // {
+ // type: "array",
+ // items: {
+ // type: "number",
+ // },
+ // },
+ // ],
+ // };
+
+ // expect(
+ // await Promise.all(
+ // createNodes(schema, "response").map(
+ // async (md: any) => await prettier.format(md, { parser: "babel" })
+ // )
+ // )
+ // ).toMatchSnapshot();
+ // });
+
+ it("should correctly deep merge properties in allOf schemas", async () => {
+ const schema: SchemaObject = {
+ allOf: [
+ {
+ type: "object",
+ properties: {
+ deepProp: {
+ type: "object",
+ properties: {
+ innerProp1: {
+ type: "string",
+ },
+ },
+ },
},
- {
- type: "string",
+ },
+ {
+ type: "object",
+ properties: {
+ deepProp: {
+ type: "object",
+ properties: {
+ innerProp2: {
+ type: "number",
+ },
+ },
+ },
},
- ],
- },
- },
- };
- expect(
- await Promise.all(
- createNodes(schema, "request").map(
- async (md: any) => await prettier.format(md, { parser: "babel" })
+ },
+ ],
+ };
+
+ expect(
+ await Promise.all(
+ createNodes(schema, "response").map(
+ async (md: any) => await prettier.format(md, { parser: "babel" })
+ )
)
- )
- ).toMatchSnapshot();
+ ).toMatchSnapshot();
+ });
+
+ // eslint-disable-next-line jest/no-commented-out-tests
+ // it("should handle discriminator with allOf schemas", async () => {
+ // const schema: SchemaObject = {
+ // allOf: [
+ // {
+ // type: "object",
+ // discriminator: {
+ // propertyName: "type",
+ // },
+ // properties: {
+ // type: {
+ // type: "string",
+ // },
+ // },
+ // },
+ // {
+ // type: "object",
+ // properties: {
+ // specificProp: {
+ // type: "string",
+ // },
+ // },
+ // },
+ // ],
+ // };
+
+ // expect(
+ // await Promise.all(
+ // createNodes(schema, "response").map(
+ // async (md: any) => await prettier.format(md, { parser: "babel" })
+ // )
+ // )
+ // ).toMatchSnapshot();
+ // });
});
describe("additionalProperties", () => {
diff --git a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
index 79feea1d0..a0cc0775a 100644
--- a/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
+++ b/packages/docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
@@ -615,6 +615,7 @@ function createEdges({
}
const schemaName = getSchemaName(schema);
+
if (discriminator !== undefined && discriminator.propertyName === name) {
return createPropertyDiscriminator(
name,
@@ -635,6 +636,47 @@ function createEdges({
);
}
+ if (schema.properties !== undefined) {
+ return createDetailsNode(
+ name,
+ schemaName,
+ schema,
+ required,
+ schema.nullable
+ );
+ }
+
+ if (schema.additionalProperties !== undefined) {
+ return createDetailsNode(
+ name,
+ schemaName,
+ schema,
+ required,
+ schema.nullable
+ );
+ }
+
+ // array of objects
+ if (schema.items?.properties !== undefined) {
+ return createDetailsNode(
+ name,
+ schemaName,
+ schema,
+ required,
+ schema.nullable
+ );
+ }
+
+ if (schema.items?.anyOf !== undefined || schema.items?.oneOf !== undefined) {
+ return createDetailsNode(
+ name,
+ schemaName,
+ schema,
+ required,
+ schema.nullable
+ );
+ }
+
if (schema.allOf !== undefined) {
const { mergedSchemas }: { mergedSchemas: SchemaObject } = mergeAllOf(
schema.allOf
@@ -707,47 +749,6 @@ function createEdges({
});
}
- if (schema.properties !== undefined) {
- return createDetailsNode(
- name,
- schemaName,
- schema,
- required,
- schema.nullable
- );
- }
-
- if (schema.additionalProperties !== undefined) {
- return createDetailsNode(
- name,
- schemaName,
- schema,
- required,
- schema.nullable
- );
- }
-
- // array of objects
- if (schema.items?.properties !== undefined) {
- return createDetailsNode(
- name,
- schemaName,
- schema,
- required,
- schema.nullable
- );
- }
-
- if (schema.items?.anyOf !== undefined || schema.items?.oneOf !== undefined) {
- return createDetailsNode(
- name,
- schemaName,
- schema,
- required,
- schema.nullable
- );
- }
-
// primitives and array of non-objects
return create("SchemaItem", {
collapsible: false,
@@ -787,6 +788,19 @@ export function createNodes(
nodes.push(createAnyOneOf(schema));
}
+ if (schema.properties !== undefined) {
+ nodes.push(createProperties(schema));
+ }
+
+ if (schema.additionalProperties !== undefined) {
+ nodes.push(createAdditionalProperties(schema));
+ }
+
+ // TODO: figure out how to handle array of objects
+ if (schema.items !== undefined) {
+ nodes.push(createItems(schema));
+ }
+
if (schema.allOf !== undefined) {
const { mergedSchemas } = mergeAllOf(schema.allOf);
@@ -802,19 +816,6 @@ export function createNodes(
}
}
- if (schema.properties !== undefined) {
- nodes.push(createProperties(schema));
- }
-
- if (schema.additionalProperties !== undefined) {
- nodes.push(createAdditionalProperties(schema));
- }
-
- // TODO: figure out how to handle array of objects
- if (schema.items !== undefined) {
- nodes.push(createItems(schema));
- }
-
if (nodes.length && nodes.length > 0) {
return nodes.filter(Boolean).flat();
}