Skip to content

Commit 27c9370

Browse files
authored
support conditional ts enums (#2433)
* support conditional ts enums * simplify enum test * fix test context pollution * add changeset * add baseline test for disabled enums flag * lint fix
1 parent 89843b0 commit 27c9370

8 files changed

Lines changed: 382 additions & 4 deletions

File tree

.changeset/every-roses-tickle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": minor
3+
---
4+
5+
Conditionally generate TS enums

docs/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ The following flags are supported in the CLI:
113113
| `--empty-objects-unknown` | | `false` | Allow arbitrary properties for schema objects with no specified properties, and no specified `additionalProperties` |
114114
| `--enum` | | `false` | Generate true [TS enums](https://www.typescriptlang.org/docs/handbook/enums.html) rather than string unions. |
115115
| `--enum-values` | | `false` | Export enum values as arrays. |
116+
| `--conditional-enums` | | `false` | Only generate true TS enums when the `x-enum-*` metadata is available. Requires `--enum=true` to be enabled. |
116117
| `--dedupe-enums` | | `false` | Dedupe enum types when `--enum=true` is set |
117118
| `--check` | | `false` | Check that the generated types are up-to-date. |
118119
| `--exclude-deprecated` | | `false` | Exclude deprecated fields from types |

packages/openapi-typescript/bin/cli.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Options
1717
--output, -o Specify output file (if not specified in redocly.yaml)
1818
--enum Export true TS enums instead of unions
1919
--enum-values Export enum values as arrays
20+
--conditional-enums Only generate true TS enums when enum metadata is available (default: false)
2021
--dedupe-enums Dedupe enum types when \`--enum=true\` is set
2122
--check Check that the generated types are up-to-date. (default: false)
2223
--export-type, -t Export top-level \`type\` instead of \`interface\`
@@ -75,6 +76,7 @@ const flags = parser(args, {
7576
"emptyObjectsUnknown",
7677
"enum",
7778
"enumValues",
79+
"conditionalEnums",
7880
"dedupeEnums",
7981
"check",
8082
"excludeDeprecated",
@@ -140,6 +142,7 @@ async function generateSchema(schema, { redocly, silent = false }) {
140142
emptyObjectsUnknown: flags.emptyObjectsUnknown,
141143
enum: flags.enum,
142144
enumValues: flags.enumValues,
145+
conditionalEnums: flags.conditionalEnums,
143146
dedupeEnums: flags.dedupeEnums,
144147
excludeDeprecated: flags.excludeDeprecated,
145148
exportType: flags.exportType,

packages/openapi-typescript/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export default async function openapiTS(
7575
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
7676
enum: options.enum ?? false,
7777
enumValues: options.enumValues ?? false,
78+
conditionalEnums: options.conditionalEnums ?? false,
7879
dedupeEnums: options.dedupeEnums ?? false,
7980
excludeDeprecated: options.excludeDeprecated ?? false,
8081
exportType: options.exportType ?? false,

packages/openapi-typescript/src/transform/schema-object.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,7 @@ export function transformSchemaObjectWithComposition(
9898
!("additionalProperties" in schemaObject)
9999
) {
100100
// hoist enum to top level if string/number enum and option is enabled
101-
if (
102-
options.ctx.enum &&
103-
schemaObject.enum.every((v) => typeof v === "string" || typeof v === "number" || v === null)
104-
) {
101+
if (shouldTransformToTsEnum(options, schemaObject)) {
105102
let enumName = parseRef(options.path ?? "").pointer.join("/");
106103
// allow #/components/schemas to have simpler names
107104
enumName = enumName.replace("components/schemas", "");
@@ -304,6 +301,35 @@ export function transformSchemaObjectWithComposition(
304301
return finalType;
305302
}
306303

304+
/**
305+
* Check if the given OAPI enum should be transformed to a TypeScript enum
306+
*/
307+
function shouldTransformToTsEnum(options: TransformNodeOptions, schemaObject: SchemaObject): boolean {
308+
// Enum conversion not enabled or no enum present
309+
if (!options.ctx.enum || !schemaObject.enum) {
310+
return false;
311+
}
312+
313+
// Enum must have string, number or null values
314+
if (!schemaObject.enum.every((v) => ["string", "number", null].includes(typeof v))) {
315+
return false;
316+
}
317+
318+
// If conditionalEnums is enabled, only convert if x-enum-* metadata is present
319+
if (options.ctx.conditionalEnums) {
320+
const hasEnumMetadata =
321+
Array.isArray(schemaObject["x-enum-varnames"]) ||
322+
Array.isArray(schemaObject["x-enumNames"]) ||
323+
Array.isArray(schemaObject["x-enum-descriptions"]) ||
324+
Array.isArray(schemaObject["x-enumDescriptions"]);
325+
if (!hasEnumMetadata) {
326+
return false;
327+
}
328+
}
329+
330+
return true;
331+
}
332+
307333
/**
308334
* Handle SchemaObject minus composition (anyOf/allOf/oneOf)
309335
*/

packages/openapi-typescript/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,8 @@ export interface OpenAPITSOptions {
658658
enum?: boolean;
659659
/** Export union values as arrays */
660660
enumValues?: boolean;
661+
/** Only generate TS Enums when `x-enum-*` metadata is available */
662+
conditionalEnums?: boolean;
661663
/** Dedupe enum values */
662664
dedupeEnums?: boolean;
663665
/** (optional) Substitute path parameter names with their respective types */
@@ -695,6 +697,7 @@ export interface GlobalContext {
695697
emptyObjectsUnknown: boolean;
696698
enum: boolean;
697699
enumValues: boolean;
700+
conditionalEnums: boolean;
698701
dedupeEnums: boolean;
699702
excludeDeprecated: boolean;
700703
exportType: boolean;

packages/openapi-typescript/test/test-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const DEFAULT_CTX: GlobalContext = {
1515
emptyObjectsUnknown: false,
1616
enum: false,
1717
enumValues: false,
18+
conditionalEnums: false,
1819
dedupeEnums: false,
1920
excludeDeprecated: false,
2021
exportType: false,

0 commit comments

Comments
 (0)