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 (for example,
+,-, and.) are converted to words (likePlus,Minus, andDot).
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
- FOOResulting enum values will be FOO_LOWER, FOO_MIXED, and FOO_UPPER.
If unique names cannot be resolved, a validation error will prompt you to resolve conflicts, potentially using the x-speakeasy-enums extension.
The recommended approach is to use the map format, which prevents length mismatch errors:
schema:
type: integer
enum:
- 1
- 2
- 3
x-speakeasy-enums:
1: NOT_STARTED
2: IN_PROGRESS
3: COMPLETEAlternatively, you can use the array format, but ensure the order in the enum array corresponds exactly to the custom names in the x-speakeasy-enums array:
schema:
type: integer
enum:
- 1
- 2
- 3
x-speakeasy-enums:
- NOT_STARTED
- IN_PROGRESS
- COMPLETEEnum 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
- FOOThis schema 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 is given the original name, while the child schema’s name is concatenated with the parent’s name.
For example, the following schema:
enum_field:
oneOf:
- type: string
- type: string
enum:
- foo
- FOO
x-speakeasy-name-override: enum_fieldResults in:
class EnumFieldEnumField(str, Enum):
FOO_LOWER = 'value'
FOO_UPPER = 'value'To avoid naming conflicts, additional overrides may be necessary. For example:
enum_field:
x-speakeasy-name-override: enum_field_parent
oneOf:
- type: string
- type: string
enum:
- foo
- Foo
x-speakeasy-name-override: enum_fieldThis 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, TypeScript, Java and C# 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
- blueWhen 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 returned to the user in a type-safe manner.
Here’s how the BackgroundColor model translates to different languages:
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, specify
the preferred style using the enumFormat option in the
.speakeasy/gen.yaml config file where the SDK is generated.
For example, in the gen.yaml file:
typescript:
enumFormat: unionThis will cause all enums to be generated as a union of strings:
type Color = "sky-blue" | "dark-gray" | "stone";import { SDK } from "cool-sdk";
const sdk = new SDK();
const result = await sdk.themes.create({
name: "flashy",
background: "dark-gray",
});Whereas this:
typescript:
enumFormat: enumWill result in the following output:
enum Color {
SkyBlue = "sky-blue",
DarkGray = "dark-gray",
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.DarkGray,
});The main trade-offs to consider between the two styles are that literal unions do not require SDK users to import any additional types or values from the SDK package. The user starts 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 customize 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 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:
1: Red
2: Green
3: BlueIn 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.
Enum value descriptions
Use the x-speakeasy-enum-descriptions extension to add descriptions to enum values that will appear as documentation in the generated SDK code. This is particularly useful for providing context about each enum option to SDK users through IDE hints and generated documentation.
The extension supports two formats:
Array format
Provide descriptions as an array that corresponds to the enum values in order:
components:
schemas:
ProgrammingLanguage:
description: Programming language environment for the generated code.
type: string
x-speakeasy-enum-descriptions:
- Python >= 3.10, with numpy and simpy available.
- TypeScript >= 4.0
enum:
- PYTHON
- TYPESCRIPTA lint rule ensures that the x-speakeasy-enum-descriptions array must be the same length as the enum array.
Map format
Alternatively, use a map format where keys are enum values:
components:
schemas:
ProgrammingLanguage:
description: Programming language environment for the generated code.
type: string
x-speakeasy-enum-descriptions:
PYTHON: Python >= 3.10, with numpy and simpy available.
TYPESCRIPT: TypeScript >= 4.0
enum:
- PYTHON
- TYPESCRIPTNote
The map format is the preferred method when using overlays. If a new enum member is added to the OpenAPI specification that isn’t in the
x-speakeasy-enum-descriptions map, it will only cause a lint warning rather than a lint failure during generation.
Both formats generate the same output with JSDoc comments:
/**
* Programming language environment for the generated code.
*/
export enum ProgrammingLanguage {
/**
* Python >= 3.10, with numpy and simpy available.
*/
PYTHON = "PYTHON",
/**
* TypeScript >= 4.0
*/
TYPESCRIPT = "TYPESCRIPT",
}Note
This feature works primarily with enumFormat: enum. When using native enum types, the descriptions will be rendered as JSDoc comments that provide IDE hints and appear in generated documentation. When rendered into literal unions, whilst the description is available above the literal value in source-code, our testing implied most language servers / IDEs will not automatically present it to the user.
Last updated on