From c554e172fa05d9741ec4eb2dceef654aaf51eafa Mon Sep 17 00:00:00 2001 From: "takaoka.daisuke" Date: Wed, 28 May 2025 19:04:07 +0900 Subject: [PATCH] feat: support allOf, anyOf, and oneOf schemas with properties in array items --- demo/examples/tests/allOf.yaml | 86 +++++++++++++++ demo/examples/tests/anyOf.yaml | 60 +++++++++++ demo/examples/tests/oneOf.yaml | 60 +++++++++++ .../src/openapi/createRequestExample.ts | 27 ++++- .../src/openapi/createResponseExample.ts | 27 ++++- .../src/theme/Schema/index.tsx | 100 +++++------------- 6 files changed, 283 insertions(+), 77 deletions(-) diff --git a/demo/examples/tests/allOf.yaml b/demo/examples/tests/allOf.yaml index d1794939b..9b58d21d7 100644 --- a/demo/examples/tests/allOf.yaml +++ b/demo/examples/tests/allOf.yaml @@ -243,6 +243,76 @@ paths: items: $ref: "#/components/schemas/Book" + /allof-with-properties-in-array-item: + get: + tags: + - allOf + summary: allOf with Properties in Array Item + description: | + A list of books demonstrating allOf with properties in array item. + + Schema: + ```yaml + type: array + items: + $ref: '#/components/schemas/Book' + ``` + + Schema Components: + ```yaml + BookBase: + type: object + required: + - id + - title + - author + properties: + id: + type: integer + format: int64 + description: Unique identifier for the book + title: + type: string + description: The title of the book + author: + type: string + description: The author of the book + + AdditionalBookInfo: + type: object + properties: + publishedDate: + type: string + format: date + description: The date the book was published + genre: + type: string + description: The genre of the book + tags: + type: array + items: + type: string + description: Tags associated with the book + + CategorizedBook: + allOf: + - $ref: '#/components/schemas/BookBase' + - $ref: '#/components/schemas/AdditionalBookInfo' + properties: + category: + type: string + description: The category of the book + ``` + responses: + "200": + description: A list of books + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/CategorizedBook" + /allof-nested: get: tags: @@ -320,12 +390,15 @@ components: type: integer format: int64 description: Unique identifier for the book + example: 1234567890 title: type: string description: The title of the book + example: "The Great Gatsby" author: type: string description: The author of the book + example: "F. Scott Fitzgerald" AdditionalBookInfo: type: object @@ -334,16 +407,29 @@ components: type: string format: date description: The date the book was published + example: "2021-01-01" genre: type: string description: The genre of the book + example: "Fiction" tags: type: array items: type: string description: Tags associated with the book + example: ["Fiction", "Mystery"] Book: allOf: - $ref: "#/components/schemas/BookBase" - $ref: "#/components/schemas/AdditionalBookInfo" + + CategorizedBook: + allOf: + - $ref: "#/components/schemas/BookBase" + - $ref: "#/components/schemas/AdditionalBookInfo" + properties: + category: + type: string + description: The category of the book + example: "Fiction" diff --git a/demo/examples/tests/anyOf.yaml b/demo/examples/tests/anyOf.yaml index a19581e8f..bb32e6b57 100644 --- a/demo/examples/tests/anyOf.yaml +++ b/demo/examples/tests/anyOf.yaml @@ -61,3 +61,63 @@ paths: - type: boolean title: A boolean title: A string or integer, or a boolean + + /anyof-with-properties-in-array-item: + get: + tags: + - anyOf + summary: anyOf with Properties in Array Item + description: | + Schema: + ```yaml + type: array + items: + type: object + anyOf: + - type: object + title: Item + properties: + orderNo: + type: string + example: "123456" + - type: object + title: Error + properties: + error: + type: string + example: "Invalid order number" + properties: + name: + type: string + example: pencil + required: + - orderNo + ``` + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + anyOf: + - type: object + title: Item + properties: + orderNo: + type: string + example: "123456" + - type: object + title: Error + properties: + error: + type: string + example: "Invalid order number" + properties: + name: + type: string + example: pencil + required: + - orderNo diff --git a/demo/examples/tests/oneOf.yaml b/demo/examples/tests/oneOf.yaml index 13fea2d85..b1bb9046d 100644 --- a/demo/examples/tests/oneOf.yaml +++ b/demo/examples/tests/oneOf.yaml @@ -262,3 +262,63 @@ paths: requiredPropB: type: number required: ["requiredPropB"] + + /oneof-with-properties-in-array-item: + get: + tags: + - oneOf + summary: oneOf with Properties in Array Item + description: | + Schema: + ```yaml + type: array + items: + type: object + oneOf: + - type: object + title: Item + properties: + orderNo: + type: string + example: "123456" + - type: object + title: Error + properties: + error: + type: string + example: "Invalid order number" + properties: + name: + type: string + example: pencil + required: + - orderNo + ``` + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + oneOf: + - type: object + title: Item + properties: + orderNo: + type: string + example: "123456" + - type: object + title: Error + properties: + error: + type: string + example: "Invalid order number" + properties: + name: + type: string + example: pencil + required: + - orderNo diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/createRequestExample.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/createRequestExample.ts index 8e0547b0d..fb5e5f853 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/createRequestExample.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/createRequestExample.ts @@ -174,11 +174,11 @@ export const sampleRequestFromSchema = (schema: SchemaObject = {}): any => { if (type === "array") { if (Array.isArray(items?.anyOf)) { - return items?.anyOf.map((item: any) => sampleRequestFromSchema(item)); + return processArrayItems(items, "anyOf"); } if (Array.isArray(items?.oneOf)) { - return items?.oneOf.map((item: any) => sampleRequestFromSchema(item)); + return processArrayItems(items, "oneOf"); } return normalizeArray(sampleRequestFromSchema(items)); @@ -233,3 +233,26 @@ function normalizeArray(arr: any) { } return [arr]; } + +function processArrayItems( + items: SchemaObject, + schemaType: "anyOf" | "oneOf" +): any[] { + const itemsArray = items[schemaType] as SchemaObject[]; + return itemsArray.map((item: SchemaObject) => { + // If items has properties, merge them with each item + if (items.properties) { + const combinedSchema = { + ...item, + properties: { + ...items.properties, // Common properties from parent + ...item.properties, // Specific properties from this anyOf/oneOf item + }, + }; + // Remove anyOf/oneOf to prevent infinite recursion when calling sampleRequestFromSchema + delete combinedSchema[schemaType]; + return sampleRequestFromSchema(combinedSchema); + } + return sampleRequestFromSchema(item); + }); +} diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/createResponseExample.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/createResponseExample.ts index fba987517..23f5ee20f 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/createResponseExample.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/createResponseExample.ts @@ -177,11 +177,11 @@ export const sampleResponseFromSchema = (schema: SchemaObject = {}): any => { if (type === "array") { if (Array.isArray(items?.anyOf)) { - return items?.anyOf.map((item: any) => sampleResponseFromSchema(item)); + return processArrayItems(items, "anyOf"); } if (Array.isArray(items?.oneOf)) { - return items?.oneOf.map((item: any) => sampleResponseFromSchema(item)); + return processArrayItems(items, "oneOf"); } return [sampleResponseFromSchema(items)]; @@ -236,3 +236,26 @@ function normalizeArray(arr: any) { } return [arr]; } + +function processArrayItems( + items: SchemaObject, + schemaType: "anyOf" | "oneOf" +): any[] { + const itemsArray = items[schemaType] as SchemaObject[]; + return itemsArray.map((item: SchemaObject) => { + // If items has properties, merge them with each item + if (items.properties) { + const combinedSchema = { + ...item, + properties: { + ...items.properties, // Common properties from parent + ...item.properties, // Specific properties from this anyOf/oneOf item + }, + }; + // Remove anyOf/oneOf to prevent infinite recursion when calling sampleResponseFromSchema + delete combinedSchema[schemaType]; + return sampleResponseFromSchema(combinedSchema); + } + return sampleResponseFromSchema(item); + }); +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/Schema/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/Schema/index.tsx index 1694f5c6f..742ad39f3 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/Schema/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/Schema/index.tsx @@ -504,88 +504,42 @@ const Items: React.FC<{ schema: any; schemaType: "request" | "response"; }> = ({ schema, schemaType }) => { - // Handles case when schema.items has properties - if (schema.items?.properties) { - return ( - <> - - - - - ); + // Process schema.items to handle allOf merging + let itemsSchema = schema.items; + if (schema.items?.allOf) { + itemsSchema = mergeAllOf(schema.items) as SchemaObject; } - // Handles case when schema.items has additionalProperties - if (schema.items?.additionalProperties) { - return ( - <> - - - - - ); - } + // Handle complex schemas with multiple schema types + const hasOneOfAnyOf = itemsSchema?.oneOf || itemsSchema?.anyOf; + const hasProperties = itemsSchema?.properties; + const hasAdditionalProperties = itemsSchema?.additionalProperties; - // Handles case when schema.items has oneOf or anyOf - if (schema.items?.oneOf || schema.items?.anyOf) { + if (hasOneOfAnyOf || hasProperties || hasAdditionalProperties) { return ( <> - + {hasOneOfAnyOf && ( + + )} + {hasProperties && ( + + )} + {hasAdditionalProperties && ( + + )} ); } - // Handles case when schema.items has allOf - if (schema.items?.allOf) { - const mergedSchemas = mergeAllOf(schema.items) as SchemaObject; - - // Handles combo anyOf/oneOf + properties - if ( - (mergedSchemas.oneOf || mergedSchemas.anyOf) && - mergedSchemas.properties - ) { - return ( - <> - - - - - - ); - } - - // Handles only anyOf/oneOf - if (mergedSchemas.oneOf || mergedSchemas.anyOf) { - return ( - <> - - - - - ); - } - - // Handles properties - if (mergedSchemas.properties) { - return ( - <> - - - - - ); - } - } - // Handles basic types (string, number, integer, boolean, object) if ( - schema.items?.type === "string" || - schema.items?.type === "number" || - schema.items?.type === "integer" || - schema.items?.type === "boolean" || - schema.items?.type === "object" + itemsSchema?.type === "string" || + itemsSchema?.type === "number" || + itemsSchema?.type === "integer" || + itemsSchema?.type === "boolean" || + itemsSchema?.type === "object" ) { return (
@@ -593,9 +547,9 @@ const Items: React.FC<{ @@ -608,7 +562,7 @@ const Items: React.FC<{ return ( <> - {Object.entries(schema.items || {}).map(([key, val]: [string, any]) => ( + {Object.entries(itemsSchema || {}).map(([key, val]: [string, any]) => (