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
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.yamlcomponents: 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.