Skip to content

Commit 6cd891e

Browse files
authored
fix(theme): show dropdown for enum parameters wrapped in allOf (#1301)
Add support for detecting enum values inside allOf schemas when rendering parameter form inputs and schema documentation. Previously, enums wrapped in allOf (e.g., for nullable enum refs) would incorrectly render as text inputs and show "object" type instead of displaying the actual enum values. Changes: - Add getSchemaEnum() helper to ParamOptions for form input detection - Add getEnumFromSchema() and getTypeFromSchema() helpers to schema.ts - Update prettyName() to return correct type for allOf with enums - Update getQualifierMessage() to show enum values from allOf - Update ParamSelectFormItem to extract enum from allOf - Update ParamMultiSelectFormItem to extract enum from allOf for arrays - Add test case in demo/examples/tests/allOf.yaml - Add servers to allOf.yaml spec for API Explorer testing - Enable API Explorer for test specs (hideSendButton: false) Fixes #1233
1 parent 0f643dd commit 6cd891e

File tree

6 files changed

+165
-17
lines changed

6 files changed

+165
-17
lines changed

demo/docusaurus.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ const config: Config = {
366366
groupPathsBy: "tag",
367367
categoryLinkSource: "info",
368368
},
369-
hideSendButton: true,
369+
hideSendButton: false,
370370
showSchemas: true,
371371
} satisfies OpenApiPlugin.Options,
372372
} satisfies Plugin.PluginOptions,

demo/examples/tests/allOf.yaml

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ info:
33
title: AllOf Variations API
44
description: Demonstrates various allOf schema combinations.
55
version: 1.0.0
6+
servers:
7+
- url: https://api.example.com/v1
8+
description: Example API server
69
tags:
710
- name: allOf
811
description: allOf tests
@@ -412,6 +415,66 @@ paths:
412415
required:
413416
- pet
414417

418+
/allof-enum-parameter:
419+
get:
420+
tags:
421+
- allOf
422+
summary: allOf with Enum Parameter
423+
description: |
424+
Demonstrates that enum parameters wrapped in allOf should render as a dropdown/combobox
425+
instead of a text input.
426+
427+
Parameter Schema:
428+
```yaml
429+
name: status
430+
in: query
431+
schema:
432+
nullable: true
433+
allOf:
434+
- $ref: '#/components/schemas/TaskStatus'
435+
```
436+
437+
Where TaskStatus is:
438+
```yaml
439+
TaskStatus:
440+
type: string
441+
enum: [Pending, InProgress, Completed, Cancelled]
442+
```
443+
parameters:
444+
- name: status
445+
in: query
446+
description: Filter by task status (should show as dropdown)
447+
schema:
448+
nullable: true
449+
allOf:
450+
- $ref: "#/components/schemas/TaskStatus"
451+
- name: priorities
452+
in: query
453+
description: Filter by multiple priorities (should show as multi-select)
454+
schema:
455+
type: array
456+
items:
457+
allOf:
458+
- $ref: "#/components/schemas/Priority"
459+
responses:
460+
"200":
461+
description: A list of tasks
462+
content:
463+
application/json:
464+
schema:
465+
type: array
466+
items:
467+
type: object
468+
properties:
469+
id:
470+
type: integer
471+
name:
472+
type: string
473+
status:
474+
$ref: "#/components/schemas/TaskStatus"
475+
priority:
476+
$ref: "#/components/schemas/Priority"
477+
415478
/allof-multiple-oneof:
416479
post:
417480
tags:
@@ -613,14 +676,24 @@ paths:
613676

614677
components:
615678
schemas:
616-
# Your existing schemas are integrated here.
617-
ExistingSchema1:
618-
type: object
619-
properties: ...
679+
# Enum schemas for allOf parameter tests
680+
TaskStatus:
681+
type: string
682+
description: The status of a task
683+
enum:
684+
- Pending
685+
- InProgress
686+
- Completed
687+
- Cancelled
620688

621-
ExistingSchema2:
622-
type: object
623-
properties: ...
689+
Priority:
690+
type: string
691+
description: Priority level
692+
enum:
693+
- Low
694+
- Medium
695+
- High
696+
- Critical
624697

625698
# New schemas for Books demonstration
626699
BookBase:

packages/docusaurus-theme-openapi-docs/src/markdown/schema.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,44 @@ import { translate } from "@docusaurus/Translate";
1010
import { OPENAPI_SCHEMA_ITEM } from "../theme/translationIds";
1111
import { SchemaObject } from "../types";
1212

13+
/**
14+
* Extracts enum values from a schema, including when wrapped in allOf.
15+
*/
16+
function getEnumFromSchema(schema: SchemaObject): any[] | undefined {
17+
if (schema.enum) {
18+
return schema.enum;
19+
}
20+
21+
if (schema.allOf && Array.isArray(schema.allOf)) {
22+
for (const item of schema.allOf) {
23+
if (item.enum) {
24+
return item.enum;
25+
}
26+
}
27+
}
28+
29+
return undefined;
30+
}
31+
32+
/**
33+
* Extracts the type from a schema, including when wrapped in allOf.
34+
*/
35+
function getTypeFromSchema(schema: SchemaObject): string | undefined {
36+
if (schema.type) {
37+
return schema.type as string;
38+
}
39+
40+
if (schema.allOf && Array.isArray(schema.allOf)) {
41+
for (const item of schema.allOf) {
42+
if (item.type) {
43+
return item.type as string;
44+
}
45+
}
46+
}
47+
48+
return undefined;
49+
}
50+
1351
function prettyName(schema: SchemaObject, circular?: boolean) {
1452
// Handle enum-only schemas (valid in JSON Schema)
1553
// When enum is present without explicit type, treat as string
@@ -28,6 +66,12 @@ function prettyName(schema: SchemaObject, circular?: boolean) {
2866
return schema.allOf[0];
2967
}
3068
}
69+
// Check if allOf contains an enum - if so, return the type from allOf
70+
const enumFromAllOf = getEnumFromSchema(schema);
71+
if (enumFromAllOf) {
72+
const typeFromAllOf = getTypeFromSchema(schema);
73+
return typeFromAllOf ?? "string";
74+
}
3175
return "object";
3276
}
3377

@@ -92,10 +136,12 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
92136

93137
let qualifierGroups = [];
94138

95-
if (schema.items && schema.items.enum) {
96-
if (schema.items.enum) {
139+
// Check for enum in array items (directly or inside allOf)
140+
if (schema.items) {
141+
const itemsEnum = getEnumFromSchema(schema.items as SchemaObject);
142+
if (itemsEnum) {
97143
qualifierGroups.push(
98-
`[${schema.items.enum.map((e) => `\`${e}\``).join(", ")}]`
144+
`[${itemsEnum.map((e) => `\`${e}\``).join(", ")}]`
99145
);
100146
}
101147
}
@@ -177,8 +223,10 @@ export function getQualifierMessage(schema?: SchemaObject): string | undefined {
177223
qualifierGroups.push(`[${values.map((e) => `\`${e}\``).join(", ")}]`);
178224
}
179225

180-
if (schema.enum) {
181-
qualifierGroups.push(`[${schema.enum.map((e) => `\`${e}\``).join(", ")}]`);
226+
// Check for enum directly on schema or inside allOf
227+
const schemaEnum = getEnumFromSchema(schema);
228+
if (schemaEnum) {
229+
qualifierGroups.push(`[${schemaEnum.map((e) => `\`${e}\``).join(", ")}]`);
182230
}
183231

184232
if (schema.minItems) {

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React from "react";
1010
import { translate } from "@docusaurus/Translate";
1111
import { ErrorMessage } from "@hookform/error-message";
1212
import FormMultiSelect from "@theme/ApiExplorer/FormMultiSelect";
13+
import { getSchemaEnum } from "@theme/ApiExplorer/ParamOptions";
1314
import { Param, setParam } from "@theme/ApiExplorer/ParamOptions/slice";
1415
import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
1516
import { OPENAPI_FORM } from "@theme/translationIds";
@@ -29,7 +30,7 @@ export default function ParamMultiSelectFormItem({ param }: ParamProps) {
2930

3031
const dispatch = useTypedDispatch();
3132

32-
const options = param.schema?.items?.enum ?? [];
33+
const options = getSchemaEnum(param.schema?.items) ?? [];
3334

3435
const pathParams = useTypedSelector((state: any) => state.params.path);
3536
const queryParams = useTypedSelector((state: any) => state.params.query);

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React from "react";
1010
import { translate } from "@docusaurus/Translate";
1111
import { ErrorMessage } from "@hookform/error-message";
1212
import FormSelect from "@theme/ApiExplorer/FormSelect";
13+
import { getSchemaEnum } from "@theme/ApiExplorer/ParamOptions";
1314
import { Param, setParam } from "@theme/ApiExplorer/ParamOptions/slice";
1415
import { useTypedDispatch } from "@theme/ApiItem/hooks";
1516
import { OPENAPI_FORM } from "@theme/translationIds";
@@ -29,7 +30,7 @@ export default function ParamSelectFormItem({ param }: ParamProps) {
2930

3031
const dispatch = useTypedDispatch();
3132

32-
const options = param.schema?.enum ?? [];
33+
const options = getSchemaEnum(param.schema) ?? [];
3334

3435
return (
3536
<>

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/ParamOptions/index.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,41 @@ export interface ParamProps {
2323
param: Param;
2424
}
2525

26+
/**
27+
* Extracts enum values from a schema, including when wrapped in allOf.
28+
* This handles cases where an enum is referenced via allOf for composition.
29+
*/
30+
export function getSchemaEnum(schema: any): any[] | undefined {
31+
// Direct enum on schema
32+
if (schema?.enum) {
33+
return schema.enum;
34+
}
35+
36+
// Enum inside allOf - check each item
37+
if (schema?.allOf && Array.isArray(schema.allOf)) {
38+
for (const item of schema.allOf) {
39+
if (item.enum) {
40+
return item.enum;
41+
}
42+
}
43+
}
44+
45+
return undefined;
46+
}
47+
2648
function ParamOption({ param }: ParamProps) {
27-
if (param.schema?.type === "array" && param.schema.items?.enum) {
49+
const schemaEnum = getSchemaEnum(param.schema);
50+
const itemsEnum = getSchemaEnum(param.schema?.items);
51+
52+
if (param.schema?.type === "array" && itemsEnum) {
2853
return <ParamMultiSelectFormItem param={param} />;
2954
}
3055

3156
if (param.schema?.type === "array") {
3257
return <ParamArrayFormItem param={param} />;
3358
}
3459

35-
if (param.schema?.enum) {
60+
if (schemaEnum) {
3661
return <ParamSelectFormItem param={param} />;
3762
}
3863

0 commit comments

Comments
 (0)