Customize Enums

Enum Value Naming

Basic Conversion

Enum values are named according to their values, with adjustments made to form valid identifiers:

  • Invalid characters are removed.
  • Values are converted to fit the case style of the target programming language.
  • Special characters (e.g., +, -, .) are converted to words like Plus, Minus, Dot.

Name Conflicts

If naming conflicts arise after sanitization deduplication is attempted by modifying case styles or adding suffixes.

For example given the following schema:

schema:
type: string
enum:
- foo
- Foo
- FOO

Results in enum values FOO_LOWER, FOO_MIXED, FOO_UPPER.

If unique names cannot be resolved, a validation error will prompt you to resolve conflicts, potentially using the x-speakeasy-enums extension.

schema:
type: integer
enum:
- 1
- 2
- 3
x-speakeasy-enums:
- NOT_STARTED
- IN_PROGRESS
- COMPLETE

Ensure the order in the enum array corresponds to the custom names in the x-speakeasy-enums array.

Enum Class Naming

Use the x-speakeasy-name-override attribute to customize enum class names:

Enum:
x-speakeasy-name-override: example_override
type: string
enum:
- foo
- FOO

Will produce:

class ExampleOverride(str, Enum):
FOO_LOWER = 'foo'
FOO_UPPER = 'FOO'

Name Conflict Considerations

Some cases (like open enums) may pose unique name resolutions challenges, particularly when similar names occur in the schema.

In name conflict cases the parent schema receives the original name, while the child schema’s name is concatenated with the parent’s name:

enum_field:
oneOf:
- type: string
- type: string
enum:
- foo
- FOO
x-speakeasy-name-override: enum_field

Results in:

class EnumFieldEnumField(str, Enum):
FOO_LOWER = 'value'
FOO_UPPER = 'value'

To avoid naming conflicts, additional overrides may be necessary::

enum_field:
x-speakeasy-name-override: enum_field_parent
oneOf:
- type: string
- type: string
enum:
- foo
- Foo
x-speakeasy-name-override: enum_field

Which will result in:

class EnumField(str, Enum):
FOO_LOWER = 'value'
FOO_UPPER = 'value'

Open vs Closed enums

Info Icon

NOTE

This feature is currently supported in Go, Python and TypeScript SDKs.

By default, enums defined in OpenAPI are considered closed during code generation. This means that introducing a new member to an enum can become a breaking change for consumers of older versions of the SDK. Sometimes, this is desirable because particular enums can be rigidly defined and not changing in the foreseeable future (country codes might be a good example of this).

However, in some cases, you might want to make room for future iteration and the introduction of new enum members. This is where open enums can help. Use the x-speakeasy-unknown-values extension to mark an enum as open:

components:
schemas:
BackgroundColor:
type: string
x-speakeasy-unknown-values: allow
enum:
- red
- green
- blue

When an SDK is generated with this type, the API is able to send values beyond what is specified in the enum and these will be captured and return to the user in a type-safe manner. An example of what this schema looks like in TypeScript:

type BackgroundColor = 'red' | 'green' | 'blue' | Unrecognized<string>;

Native Enums vs Union of Strings

Languages like Python and TypeScript support string or integer literal unions as well as native enum types. When generating SDKs for these targets, you can specify which style you prefer using the enumFormat option in the .speakeasy/gen.yaml config file where the SDK is generated.

For example in your gen.yaml:

typescript:
enumFormat: union

Will cause all enums to be generated as a union of strings:

type Color = "sky-blue" | "dark-grey" | "stone";
import { SDK } from "cool-sdk";
const sdk = new SDK();
const result = await sdk.themes.create({
name: "flashy",
background: "dark-grey",
});

Whereas:

typescript:
enumFormat: enum

Will result in the following output:

enum Color {
SkyBlue = 'sky-blue',
DarkGrey = 'dark-grey',
Stone = 'stone',
}
import { SDK } from "cool-sdk";
import { Color } from "cool-sdk/models/color";
const sdk = new SDK();
const result = await sdk.themes.create({
name: "flashy",
background: Color.DarkGrey,
});

The main trade-offs to consider between the two styles are that literal unions do not require SDK users to import any additional types/values from the SDK package. They’d start typing a string or number and their IDE’s autocompletion interface will suggest from the valid set of values. Native enums need to be imported from within the SDK but benefit from having members with clear names and documentation on each. This is particularly useful when you define enums that do not map well to spoken language such as enum: ["+", "_", "*", "!"]. Using the x-speakeasy-enums extension will allow you to customise the name of each native enum member.

In TypeScript and Python, native enums are nominally typed which means that users cannot pass in any string value they have or another native enum that overlaps with the desired one without triggering a type-checker error. In some of these instances, they may need to write some code to map values to native enum members.

Mixing enum formats

While enumFormat is a global setting, it is possible to mix and match the enum format on a per-schema basis by using the x-speakeasy-enum-format extension:

# `enumFormat` is set to `union` in the gen.yaml
components:
schemas:
BackgroundColor:
type: int
x-speakeasy-enum-format: enum
enum:
- 1
- 2
- 3
x-speakeasy-enums:
- Red
- Green
- Blue

In this case, the BackgroundColor enum will be generated as a native enum in the target language, while the rest of the enums will be generated as a union of values.