Skip to content

Commit 5d9f276

Browse files
committed
Refactor ts enum members
1 parent 02269a1 commit 5d9f276

File tree

5 files changed

+98
-22
lines changed

5 files changed

+98
-22
lines changed

.changeset/chilled-news-design.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"openapi-typescript": major
2+
"openapi-typescript": minor
33
---
44

55
Add support for x-enum-varnames and x-enum-descriptions

docs/src/content/docs/advanced.md

+41
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,44 @@ Cat: { type?: "cat"; } & components["schemas"]["PetCommonProperties"];
521521
_Note: you optionally could provide `discriminator.propertyName: "type"` on `Pet` ([docs](https://spec.openapis.org/oas/v3.1.0#discriminator-object)) to automatically generate the `type` key, but is less explicit._
522522

523523
While the schema permits you to use composition in any way you like, it’s good to always take a look at the generated types and see if there’s a simpler way to express your unions & intersections. Limiting the use of `oneOf` is not the only way to do that, but often yields the greatest benefits.
524+
525+
### Enum with custom names and descriptions
526+
527+
`x-enum-varnames` can be used to have another enum name for the corresponding value. This is used to define names of the enum items.
528+
529+
`x-enum-descriptions` can be used to provide an individual description for each value. This is used for comments in the code (like javadoc if the target language is java).
530+
531+
`x-enum-descriptions` and `x-enum-varnames` are each expected to be list of items containing the same number of items as enum. The order of the items in the list matters: their position is used to group them together.
532+
533+
Example:
534+
535+
```yaml
536+
ErrorCode:
537+
type: integer
538+
format: int32
539+
enum:
540+
- 100
541+
- 200
542+
- 300
543+
x-enum-varnames:
544+
- Unauthorized
545+
- AccessDenied
546+
- Unknown
547+
x-enum-descriptions:
548+
- "User is not authorized"
549+
- "User has no access to this resource"
550+
- "Something went wrong"
551+
```
552+
553+
Will result in:
554+
555+
```ts
556+
enum ErrorCode {
557+
// User is not authorized
558+
Unauthorized = 100
559+
// User has no access to this resource
560+
AccessDenied = 200
561+
// Something went wrong
562+
Unknown = 300
563+
}
564+
```

packages/openapi-typescript/src/lib/ts.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,7 @@ export function tsDedupe(types: ts.TypeNode[]): ts.TypeNode[] {
229229
export function tsEnum(
230230
name: string,
231231
members: (string | number)[],
232-
membersNames?: string[],
233-
membersDescriptions?: string[],
232+
metadata?: { name?: string; description?: string }[],
234233
options?: { readonly?: boolean; export?: boolean },
235234
) {
236235
let enumName = name.replace(JS_ENUM_INVALID_CHARS_RE, (c) => {
@@ -252,18 +251,17 @@ export function tsEnum(
252251
: undefined,
253252
/* name */ enumName,
254253
/* members */ members.map((value, i) =>
255-
tsEnumMember(value, membersNames?.[i], membersDescriptions?.[i]),
254+
tsEnumMember(value, metadata?.[i]),
256255
),
257256
);
258257
}
259258

260259
/** Sanitize TS enum member expression */
261260
export function tsEnumMember(
262261
value: string | number,
263-
memberName?: string,
264-
description?: string,
262+
metadata: { name?: string; description?: string } = {},
265263
) {
266-
let name = memberName ?? String(value);
264+
let name = metadata.name ?? String(value);
267265
if (!JS_PROPERTY_INDEX_RE.test(name)) {
268266
if (Number(name[0]) >= 0) {
269267
name = `Value${name}`.replace(".", "_"); // don't forged decimals;
@@ -284,14 +282,14 @@ export function tsEnumMember(
284282
);
285283
}
286284

287-
if (description == undefined) {
285+
if (metadata.description == undefined) {
288286
return member;
289287
}
290288

291289
return ts.addSyntheticLeadingComment(
292290
member,
293291
ts.SyntaxKind.SingleLineCommentTrivia,
294-
" ".concat(description.trim()),
292+
" ".concat(metadata.description.trim()),
295293
true,
296294
);
297295
}

packages/openapi-typescript/src/transform/schema-object.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,14 @@ export function transformSchemaObjectWithComposition(
124124
let enumName = parseRef(options.path ?? "").pointer.join("/");
125125
// allow #/components/schemas to have simpler names
126126
enumName = enumName.replace("components/schemas", "");
127+
const metadata = schemaObject.enum.map((_, i) => ({
128+
name: schemaObject["x-enum-varnames"]?.[i],
129+
description: schemaObject["x-enum-descriptions"]?.[i],
130+
}));
127131
const enumType = tsEnum(
128132
enumName,
129133
schemaObject.enum as (string | number)[],
130-
schemaObject["x-enum-varnames"],
131-
schemaObject["x-enum-descriptions"],
134+
metadata,
132135
{ export: true, readonly: options.ctx.immutable },
133136
);
134137
options.ctx.injectFooter.push(enumType);

packages/openapi-typescript/test/lib/ts.test.ts

+45-11
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,15 @@ describe("tsEnum", () => {
133133
it("number members with x-enum-descriptions", () => {
134134
expect(
135135
astToString(
136-
tsEnum(".Error.code.", [100, 101, 102], undefined, [
137-
"Code 100",
138-
"Code 101",
139-
"Code 102",
140-
]),
136+
tsEnum(
137+
".Error.code.",
138+
[100, 101, 102],
139+
[
140+
{ description: "Code 100" },
141+
{ description: "Code 101" },
142+
{ description: "Code 102" },
143+
],
144+
),
141145
).trim(),
142146
).toBe(`enum ErrorCode {
143147
// Code 100
@@ -155,7 +159,11 @@ describe("tsEnum", () => {
155159
tsEnum(
156160
".Error.code.",
157161
[100, 101, 102],
158-
["Unauthorized", "NotFound", "PermissionDenied"],
162+
[
163+
{ name: "Unauthorized" },
164+
{ name: "NotFound" },
165+
{ name: "PermissionDenied" },
166+
],
159167
),
160168
).trim(),
161169
).toBe(`enum ErrorCode {
@@ -168,7 +176,11 @@ describe("tsEnum", () => {
168176
it("x-enum-varnames with numeric prefix", () => {
169177
expect(
170178
astToString(
171-
tsEnum(".Error.code.", [100, 101, 102], ["0a", "1b", "2c"]),
179+
tsEnum(
180+
".Error.code.",
181+
[100, 101, 102],
182+
[{ name: "0a" }, { name: "1b" }, { name: "2c" }],
183+
),
172184
).trim(),
173185
).toBe(`enum ErrorCode {
174186
Value0a = 100,
@@ -177,17 +189,39 @@ describe("tsEnum", () => {
177189
}`);
178190
});
179191

192+
it("partial x-enum-varnames and x-enum-descriptions", () => {
193+
expect(
194+
astToString(
195+
tsEnum(
196+
".Error.code.",
197+
[100, 101, 102],
198+
[
199+
{ name: "Unauthorized", description: "User is unauthorized" },
200+
{ name: "NotFound" },
201+
],
202+
),
203+
).trim(),
204+
).toBe(`enum ErrorCode {
205+
// User is unauthorized
206+
Unauthorized = 100,
207+
NotFound = 101,
208+
Value102 = 102
209+
}`);
210+
});
211+
180212
it("x-enum-descriptions with x-enum-varnames", () => {
181213
expect(
182214
astToString(
183215
tsEnum(
184216
".Error.code.",
185217
[100, 101, 102],
186-
["Unauthorized", "NotFound", "PermissionDenied"],
187218
[
188-
"User is unauthorized",
189-
"Item not found",
190-
"User doesn't have permissions",
219+
{ name: "Unauthorized", description: "User is unauthorized" },
220+
{ name: "NotFound", description: "Item not found" },
221+
{
222+
name: "PermissionDenied",
223+
description: "User doesn't have permissions",
224+
},
191225
],
192226
),
193227
).trim(),

0 commit comments

Comments
 (0)