Skip to content

Commit 5465aae

Browse files
committed
feat: allow for enums to be deprecated
Ticket: DX-2535 This commit enables the ability to mark individual enum variants as deprecated
1 parent 000adde commit 5465aae

File tree

6 files changed

+204
-3
lines changed

6 files changed

+204
-3
lines changed

packages/openapi-generator/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,40 @@ const Schema = t.type({
497497
fieldWithFormattedDescription: t.string,
498498
});
499499
```
500+
501+
#### 6.2.4 Deprecated Enum Values
502+
503+
When using `t.keyof` to define enums, you can mark specific enum values as deprecated
504+
using the `@deprecated` tag. Deprecated values will be collected into an
505+
`x-enumsDeprecated` array in the OpenAPI specification.
506+
507+
```typescript
508+
import * as t from 'io-ts';
509+
510+
/**
511+
* Transaction status values
512+
*/
513+
export const TransactionStatus = t.keyof(
514+
{
515+
pendingApproval: 1,
516+
/** @deprecated */
517+
canceled: 1,
518+
/** @deprecated */
519+
rejected: 1,
520+
completed: 1,
521+
},
522+
'TransactionStatus',
523+
);
524+
```
525+
526+
This will generate the following OpenAPI schema:
527+
528+
```json
529+
{
530+
"TransactionStatus": {
531+
"type": "string",
532+
"enum": ["pendingApproval", "canceled", "rejected", "completed"],
533+
"x-enumsDeprecated": ["canceled", "rejected"]
534+
}
535+
}
536+
```

packages/openapi-generator/src/ir.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type Primitive = {
1414
type: 'string' | 'number' | 'integer' | 'boolean' | 'null';
1515
enum?: (string | number | boolean | null | PseudoBigInt)[];
1616
enumDescriptions?: Record<string, string>;
17+
enumsDeprecated?: string[];
1718
};
1819

1920
export function isPrimitive(schema: Schema): schema is Primitive {

packages/openapi-generator/src/knownImports.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export const KNOWN_IMPORTS: KnownImports = {
129129

130130
const enumValues = Object.keys(arg.properties);
131131
const enumDescriptions: Record<string, string> = {};
132+
const enumsDeprecated: string[] = [];
132133
let hasDescriptions = false;
133134

134135
for (const prop of enumValues) {
@@ -139,14 +140,18 @@ export const KNOWN_IMPORTS: KnownImports = {
139140
enumDescriptions[prop] = jsdoc.tags.description;
140141
hasDescriptions = true;
141142
}
143+
if (jsdoc.tags && 'deprecated' in jsdoc.tags) {
144+
enumsDeprecated.push(prop);
145+
}
142146
}
143147
}
144148

145-
if (hasDescriptions) {
149+
if (hasDescriptions || enumsDeprecated.length > 0) {
146150
return E.right({
147151
type: 'string',
148152
enum: enumValues,
149-
enumDescriptions,
153+
...(hasDescriptions ? { enumDescriptions } : {}),
154+
...(enumsDeprecated.length > 0 ? { enumsDeprecated } : {}),
150155
});
151156
} else {
152157
const schemas: Schema[] = enumValues.map((prop) => {

packages/openapi-generator/src/openapi.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export function schemaToOpenAPI(
3131
result['x-enumDescriptions'] = schema.enumDescriptions;
3232
}
3333

34+
if (schema.enum && schema.enumsDeprecated) {
35+
result['x-enumsDeprecated'] = schema.enumsDeprecated;
36+
}
37+
3438
return result;
3539
}
3640
case 'integer': {
@@ -44,6 +48,10 @@ export function schemaToOpenAPI(
4448
result['x-enumDescriptions'] = schema.enumDescriptions;
4549
}
4650

51+
if (schema.enum && schema.enumsDeprecated) {
52+
result['x-enumsDeprecated'] = schema.enumsDeprecated;
53+
}
54+
4755
return result;
4856
}
4957
case 'null':

packages/openapi-generator/src/optimize.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,11 @@ export function simplifyUnion(schema: Schema, optimize: OptimizeFn): Schema {
160160
const remainder: Schema[] = [];
161161
innerSchemas.forEach((innerSchema) => {
162162
if (isPrimitive(innerSchema) && innerSchema.enum !== undefined) {
163-
if (innerSchema.comment || innerSchema.enumDescriptions) {
163+
if (
164+
innerSchema.comment ||
165+
innerSchema.enumDescriptions ||
166+
innerSchema.enumsDeprecated
167+
) {
164168
remainder.push(innerSchema);
165169
} else {
166170
innerSchema.enum.forEach((value) => {

packages/openapi-generator/test/openapi/comments.test.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,3 +1813,149 @@ testCase(
18131813
},
18141814
},
18151815
);
1816+
1817+
const ROUTE_WITH_ENUM_DEPRECATED = `
1818+
import * as t from 'io-ts';
1819+
import * as h from '@api-ts/io-ts-http';
1820+
1821+
/**
1822+
* Enum with @deprecated tags - should generate x-enumsDeprecated
1823+
*/
1824+
export const StatusWithDeprecated = t.keyof(
1825+
{
1826+
/**
1827+
* @description Transaction is waiting for approval from authorized users
1828+
*/
1829+
pendingApproval: 1,
1830+
/**
1831+
* @description Transaction was canceled by the user
1832+
* @deprecated
1833+
*/
1834+
canceled: 1,
1835+
/**
1836+
* @description Transaction was rejected by approvers
1837+
* @deprecated
1838+
*/
1839+
rejected: 1,
1840+
},
1841+
'StatusWithDeprecated',
1842+
);
1843+
1844+
/**
1845+
* Enum with only @deprecated tags - should generate x-enumsDeprecated
1846+
*/
1847+
export const StatusOnlyDeprecated = t.keyof(
1848+
{
1849+
/** @deprecated */
1850+
old: 1,
1851+
current: 1,
1852+
/** @deprecated */
1853+
legacy: 1,
1854+
},
1855+
'StatusOnlyDeprecated',
1856+
);
1857+
1858+
/**
1859+
* Route to test enum deprecated scenarios
1860+
*
1861+
* @operationId api.v1.enumDeprecatedScenarios
1862+
* @tag Test Routes
1863+
*/
1864+
export const route = h.httpRoute({
1865+
path: '/enum-deprecated',
1866+
method: 'GET',
1867+
request: h.httpRequest({
1868+
query: {
1869+
withDeprecated: StatusWithDeprecated,
1870+
onlyDeprecated: StatusOnlyDeprecated,
1871+
},
1872+
}),
1873+
response: {
1874+
200: {
1875+
result: t.string
1876+
}
1877+
},
1878+
});
1879+
`;
1880+
1881+
testCase(
1882+
'enum deprecated scenarios - @deprecated tags with and without @description',
1883+
ROUTE_WITH_ENUM_DEPRECATED,
1884+
{
1885+
openapi: '3.0.3',
1886+
info: {
1887+
title: 'Test',
1888+
version: '1.0.0',
1889+
},
1890+
paths: {
1891+
'/enum-deprecated': {
1892+
get: {
1893+
summary: 'Route to test enum deprecated scenarios',
1894+
operationId: 'api.v1.enumDeprecatedScenarios',
1895+
tags: ['Test Routes'],
1896+
parameters: [
1897+
{
1898+
name: 'withDeprecated',
1899+
in: 'query',
1900+
required: true,
1901+
schema: {
1902+
$ref: '#/components/schemas/StatusWithDeprecated',
1903+
},
1904+
},
1905+
{
1906+
name: 'onlyDeprecated',
1907+
in: 'query',
1908+
required: true,
1909+
schema: {
1910+
$ref: '#/components/schemas/StatusOnlyDeprecated',
1911+
},
1912+
},
1913+
],
1914+
responses: {
1915+
200: {
1916+
description: 'OK',
1917+
content: {
1918+
'application/json': {
1919+
schema: {
1920+
type: 'object',
1921+
properties: {
1922+
result: {
1923+
type: 'string',
1924+
},
1925+
},
1926+
required: ['result'],
1927+
},
1928+
},
1929+
},
1930+
},
1931+
},
1932+
},
1933+
},
1934+
},
1935+
components: {
1936+
schemas: {
1937+
StatusWithDeprecated: {
1938+
title: 'StatusWithDeprecated',
1939+
description: 'Enum with @deprecated tags - should generate x-enumsDeprecated',
1940+
type: 'string',
1941+
enum: ['pendingApproval', 'canceled', 'rejected'],
1942+
'x-enumDescriptions': {
1943+
pendingApproval:
1944+
'Transaction is waiting for approval from authorized users',
1945+
canceled: 'Transaction was canceled by the user',
1946+
rejected: 'Transaction was rejected by approvers',
1947+
},
1948+
'x-enumsDeprecated': ['canceled', 'rejected'],
1949+
},
1950+
StatusOnlyDeprecated: {
1951+
title: 'StatusOnlyDeprecated',
1952+
description:
1953+
'Enum with only @deprecated tags - should generate x-enumsDeprecated',
1954+
type: 'string',
1955+
enum: ['old', 'current', 'legacy'],
1956+
'x-enumsDeprecated': ['old', 'legacy'],
1957+
},
1958+
},
1959+
},
1960+
},
1961+
);

0 commit comments

Comments
 (0)