openapi-typescript
openapi-typescript copied to clipboard
[Feature request] Generate Enums AND Union types
Hey 👋 I think it would be great if we could generate Enum types and Union types all together (or at least as enum types). Basically same thing as Prisma does when generating types for database models 🚀 Wdyt?
I think this could be offered as an option. I feel strongly that the default behavior of string unions shouldn’t change, because when data’s coming from an object it’s much easier to coerce a string union than a TypeScript enum.
One question I’d like to ask is: how would you envision this working? Like, what would the import path be? The following would have to be considered:
- Enums could come from
components
as well aspaths
andresponses
- Account for deep-linking of enums (e.g.
components['schemas']['TopLevel']['NestedOne']['NestedTwo']['EnumProperty']
) - Also account for invalid TypeScript characters (
"Enum Type #1"
) - Lastly, whatever gets exported can’t have any conflicts with any other part of their schema (e.g. it would be impossible for them to add any property that conflicts)
Adding a proposal that takes into account those things would really help implementing (asking for anyone reading this thread!)
I think this could be offered as an option. I feel strongly that the default behaviour of string unions shouldn’t change, because when data’s coming from an object it’s much easier to coerce a string union than a TypeScript enum.
Absolutely agree on this 💯 . I also always go with string unions instead of enums.
But there are rare occasions where one "have to" use enum, usually that happens when we want to iterate over the values and render them on frontend (dropdown selection, table etc.) and of course we don't want to duplicate and hardcode those values on frontend, since we will need to keep them up to date all the time with backend (which is the source of truth).
Example: Role
string union type defined in swagger, where on user creation page, we select predefined user role.
I wouldn't go with an option, but instead as you mentioned default behaviour of string unions shouldn't change, lets just add enums/object exported along with it.
If we check how Prisma does it:
If database model User
and Role
are defined as
model User {
id String @id @default(uuid())
email String @unique
role Role @default(STANDARD)
}
enum Role {
STANDARD
APPRENTICE
SUPERVISOR
ADMINISTRATOR
}
Prisma CLI is going to generate types as:
export const Role: {
STANDARD: 'STANDARD',
APPRENTICE: 'APPRENTICE',
SUPERVISOR: 'SUPERVISOR',
ADMINISTRATOR: 'ADMINISTRATOR'
};
export type Role = (typeof Role)[keyof typeof Role]
And frontend can consume it as:
import { Role } from '@prisma/client';
type MyRoleType = Role; // MyRoleType is of type string union "APPRENTICE" | "STANDARD" | "SUPERVISOR" | "ADMINISTRATOR"
const myRole = Role.ADMINISTRATOR; // myRole value is "ADMINISTRATOR"
In our case of generating types from OpenAPI, I would maybe go with more explicit version, where beside generated string union Role
(as it is already implemented now), there would be another "type"/enum generate alongside, with postfix Enum
as RoleEnum
. It would be collocated with string union type (components
, paths
, responses
...).
Wdyt?
Rather than generating an enum, how about generating a concrete array of strings, and then generating the string union from that?
export const RoleStrings = [
'STANDARD',
'APPRENTICE',
'SUPERVISOR',
'ADMINISTRATOR',
] as const;
export type Roles = typeof RoleStrings[number];
This way we don't have to worry about the weird behavior of keyof enum
(which ends up containing Symbol
and number
)
Also works, just accessing of the values becomes bit less idiomatic imo, Role[0]
instead of Role.STANDARD
Maybe approach form similar library will be helpful to implement this feature.
This is a very attractive feature for the same reasons that @mkosir outlined above. My use case would be that I've form controls in the UI where the values are dictated by an API and it's documentation. Right now i'm having to manually keep those choices in sync, but an enum would give me more flexibility to reference things. I'd definitely agree that any enums generated should be "extras" and not override string unions 👍
We've moved to serializing the openapi-typescript
types and the JSON schema itself together in a single output file. This approach may serve your needs as well.
Doing so allows us to access the literal enum values, rather than just the types + unions, which is very useful both for getting access to schema union types, and for reflecting parts of the schema back to clients at run-time.
We started out using JSON module imports, but the the types from such imports are looser than we would like.
Using as const
gives us access to
import mySchema1 from 'schema.json';
const mySchema2 = {
components: {
schemas: {
Status: {
type: 'string',
enum: ['initial', 'processing', 'complete']
}
}
} as const;
mySchema1.components.schemas.Status.enum; // string[]
mySchema2.components.schemas.Status.enum; // ['initial', 'processing', 'complete']
See this comment for more details.
I wrote this: https://github.com/openapi-typescript-infra/openapi-typescript-enum which will add enums. I don't like the way I had to write it - in that wrapping the CLI with a custom transform involves copying the CLI. Would be nice to have transform/postTransform be a CLI arg that points to some node-resolvable code.
So I’ve already started planning some big changes to v7 (https://github.com/drwpow/openapi-typescript/discussions/1344) and I think that would make the enum work significantly easier.
The major blocker with enums is unlike unions they can’t be dynamically inlined in an interface. So that means hoisting out every enum
in the spec, making sure it has a unique name that doesn’t conflict, and every single reference (even nested and deep references) is wired properly (across all files for multi-file schemas), is a decent chunk of work. Not impossible; we do that in other ways. But it’s just one more layer that’s not a quick change.
But if people aren’t opposed to some minor breaking changes, if we can lean on @redocly/openapi-core
(which recently hit 1.0) for schema loading/parsing/bundling, then that greatly simplifies the work. So based on how that investigation goes, this may be a v7 feature.
This will get shipped either way, but between deep param scanning, discriminators, operations, and now enums, there are a lot of individual, overlapping efforts of “collect all the things, generate code, then reference everything properly” which Redocly could simplify greatly.
I'd definitely vote for centralizing on @redocly/openapi-core. I don't know how the community is or how the code is, but centralization in interpretation of openapi specs is undoubtedly a good thing given the intricacy of the spec and the changes that are likely to come along...
Forgot to update this issue: the --enum
flag exists in 7.x and is opt-in. This was just too complex to ship in 6.x.
7.x is still in testing and in the final bug-bashing phase, but is usable for most schemas today and will get a release candidate soon! And ICYMI it does rely on the Redocly CLI underneath for schema validation and parsing (which is lightweight and doesn’t introduce any bloat to the project).
Huzzah! Looking forward to adopting 7.x. We used redocly for our tooling too, so I'm glad to have picked the same. We're able to really slice and dice the specs this way. Now if only I could replace the Java codegen with something less horrific.
@drwpow current --enum
behavior works... bad
In most cases I get such result:
/**
* @description Status of deal
* @enum {string}
*/
DealStatus: string; // wtf? why string?
Here is my scheme
"DealStatus": {
"type": "string",
"additionalProperties": false,
"description": "Status",
"enum": [
"UNDEFINED",
"CURRENT",
"OBSERVATION",
"POTENT_PROBLEM",
"PROBLEM",
"EMPTY"
]
},
Tried with [email protected]
It's nice that v7 has tne enum flag, but I'd love to see this issue resolved (generating both)
This issue is stale because it has been open for 90 days with no activity. If there is no activity in the next 7 days, the issue will be closed.