Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/every-roses-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": minor
---

Conditionally generate TS enums
1 change: 1 addition & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ The following flags are supported in the CLI:
| `--empty-objects-unknown` | | `false` | Allow arbitrary properties for schema objects with no specified properties, and no specified `additionalProperties` |
| `--enum` | | `false` | Generate true [TS enums](https://www.typescriptlang.org/docs/handbook/enums.html) rather than string unions. |
| `--enum-values` | | `false` | Export enum values as arrays. |
| `--conditional-enums` | | `false` | Only generate true TS enums when the `x-enum-*` metadata is available. Requires `--enum=true` to be enabled. |
| `--dedupe-enums` | | `false` | Dedupe enum types when `--enum=true` is set |
| `--check` | | `false` | Check that the generated types are up-to-date. |
| `--exclude-deprecated` | | `false` | Exclude deprecated fields from types |
Expand Down
3 changes: 3 additions & 0 deletions packages/openapi-typescript/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Options
--output, -o Specify output file (if not specified in redocly.yaml)
--enum Export true TS enums instead of unions
--enum-values Export enum values as arrays
--conditional-enums Only generate true TS enums when enum metadata is available (default: false)
--dedupe-enums Dedupe enum types when \`--enum=true\` is set
--check Check that the generated types are up-to-date. (default: false)
--export-type, -t Export top-level \`type\` instead of \`interface\`
Expand Down Expand Up @@ -74,6 +75,7 @@ const flags = parser(args, {
"emptyObjectsUnknown",
"enum",
"enumValues",
"conditionalEnums",
"dedupeEnums",
"check",
"excludeDeprecated",
Expand Down Expand Up @@ -139,6 +141,7 @@ async function generateSchema(schema, { redocly, silent = false }) {
emptyObjectsUnknown: flags.emptyObjectsUnknown,
enum: flags.enum,
enumValues: flags.enumValues,
conditionalEnums: flags.conditionalEnums,
dedupeEnums: flags.dedupeEnums,
excludeDeprecated: flags.excludeDeprecated,
exportType: flags.exportType,
Expand Down
1 change: 1 addition & 0 deletions packages/openapi-typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default async function openapiTS(
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
enum: options.enum ?? false,
enumValues: options.enumValues ?? false,
conditionalEnums: options.conditionalEnums ?? false,
dedupeEnums: options.dedupeEnums ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
exportType: options.exportType ?? false,
Expand Down
34 changes: 30 additions & 4 deletions packages/openapi-typescript/src/transform/schema-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,7 @@ export function transformSchemaObjectWithComposition(
!("additionalProperties" in schemaObject)
) {
// hoist enum to top level if string/number enum and option is enabled
if (
options.ctx.enum &&
schemaObject.enum.every((v) => typeof v === "string" || typeof v === "number" || v === null)
) {
if (shouldTransformToTsEnum(options, schemaObject)) {
let enumName = parseRef(options.path ?? "").pointer.join("/");
// allow #/components/schemas to have simpler names
enumName = enumName.replace("components/schemas", "");
Expand Down Expand Up @@ -270,6 +267,35 @@ export function transformSchemaObjectWithComposition(
return finalType;
}

/**
* Check if the given OAPI enum should be transformed to a TypeScript enum
*/
function shouldTransformToTsEnum(options: TransformNodeOptions, schemaObject: SchemaObject): boolean {
// Enum conversion not enabled or no enum present
if (!options.ctx.enum || !schemaObject.enum) {
return false;
}

// Enum must have string, number or null values
if (!schemaObject.enum.every((v) => ["string", "number", null].includes(typeof v))) {
return false;
}

// If conditionalEnums is enabled, only convert if x-enum-* metadata is present
if (options.ctx.conditionalEnums) {
const hasEnumMetadata =
Array.isArray(schemaObject["x-enum-varnames"]) ||
Array.isArray(schemaObject["x-enumNames"]) ||
Array.isArray(schemaObject["x-enum-descriptions"]) ||
Array.isArray(schemaObject["x-enumDescriptions"]);
if (!hasEnumMetadata) {
return false;
}
}

return true;
}

/**
* Handle SchemaObject minus composition (anyOf/allOf/oneOf)
*/
Expand Down
3 changes: 3 additions & 0 deletions packages/openapi-typescript/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,8 @@ export interface OpenAPITSOptions {
enum?: boolean;
/** Export union values as arrays */
enumValues?: boolean;
/** Only generate TS Enums when `x-enum-*` metadata is available */
conditionalEnums?: boolean;
/** Dedupe enum values */
dedupeEnums?: boolean;
/** (optional) Substitute path parameter names with their respective types */
Expand Down Expand Up @@ -688,6 +690,7 @@ export interface GlobalContext {
emptyObjectsUnknown: boolean;
enum: boolean;
enumValues: boolean;
conditionalEnums: boolean;
dedupeEnums: boolean;
excludeDeprecated: boolean;
exportType: boolean;
Expand Down
1 change: 1 addition & 0 deletions packages/openapi-typescript/test/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const DEFAULT_CTX: GlobalContext = {
emptyObjectsUnknown: false,
enum: false,
enumValues: false,
conditionalEnums: false,
dedupeEnums: false,
excludeDeprecated: false,
exportType: false,
Expand Down
Loading
Loading