Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/openapi-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,40 @@ const Schema = t.type({
fieldWithFormattedDescription: t.string,
});
```

#### 6.2.4 Deprecated Enum Values

When using `t.keyof` to define enums, you can mark specific enum values as deprecated
using the `@deprecated` tag. Deprecated values will be collected into an
`x-enumsDeprecated` array in the OpenAPI specification.

```typescript
import * as t from 'io-ts';

/**
* Transaction status values
*/
export const TransactionStatus = t.keyof(
{
pendingApproval: 1,
/** @deprecated */
canceled: 1,
/** @deprecated */
rejected: 1,
completed: 1,
},
'TransactionStatus',
);
```

This will generate the following OpenAPI schema:

```json
{
"TransactionStatus": {
"type": "string",
"enum": ["pendingApproval", "canceled", "rejected", "completed"],
"x-enumsDeprecated": ["canceled", "rejected"]
}
}
```
1 change: 1 addition & 0 deletions packages/openapi-generator/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type Primitive = {
type: 'string' | 'number' | 'integer' | 'boolean' | 'null';
enum?: (string | number | boolean | null | PseudoBigInt)[];
enumDescriptions?: Record<string, string>;
enumsDeprecated?: string[];
};

export function isPrimitive(schema: Schema): schema is Primitive {
Expand Down
9 changes: 7 additions & 2 deletions packages/openapi-generator/src/knownImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const KNOWN_IMPORTS: KnownImports = {

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

for (const prop of enumValues) {
Expand All @@ -139,14 +140,18 @@ export const KNOWN_IMPORTS: KnownImports = {
enumDescriptions[prop] = jsdoc.tags.description;
hasDescriptions = true;
}
if (jsdoc.tags && 'deprecated' in jsdoc.tags) {
enumsDeprecated.push(prop);
}
}
}

if (hasDescriptions) {
if (hasDescriptions || enumsDeprecated.length > 0) {
return E.right({
type: 'string',
enum: enumValues,
enumDescriptions,
...(hasDescriptions ? { enumDescriptions } : {}),
...(enumsDeprecated.length > 0 ? { enumsDeprecated } : {}),
});
} else {
const schemas: Schema[] = enumValues.map((prop) => {
Expand Down
8 changes: 8 additions & 0 deletions packages/openapi-generator/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export function schemaToOpenAPI(
result['x-enumDescriptions'] = schema.enumDescriptions;
}

if (schema.enum && schema.enumsDeprecated) {
result['x-enumsDeprecated'] = schema.enumsDeprecated;
}

return result;
}
case 'integer': {
Expand All @@ -44,6 +48,10 @@ export function schemaToOpenAPI(
result['x-enumDescriptions'] = schema.enumDescriptions;
}

if (schema.enum && schema.enumsDeprecated) {
result['x-enumsDeprecated'] = schema.enumsDeprecated;
}

return result;
}
case 'null':
Expand Down
6 changes: 5 additions & 1 deletion packages/openapi-generator/src/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ export function simplifyUnion(schema: Schema, optimize: OptimizeFn): Schema {
const remainder: Schema[] = [];
innerSchemas.forEach((innerSchema) => {
if (isPrimitive(innerSchema) && innerSchema.enum !== undefined) {
if (innerSchema.comment || innerSchema.enumDescriptions) {
if (
innerSchema.comment ||
innerSchema.enumDescriptions ||
innerSchema.enumsDeprecated
) {
remainder.push(innerSchema);
} else {
innerSchema.enum.forEach((value) => {
Expand Down
146 changes: 146 additions & 0 deletions packages/openapi-generator/test/openapi/comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1813,3 +1813,149 @@ testCase(
},
},
);

const ROUTE_WITH_ENUM_DEPRECATED = `
import * as t from 'io-ts';
import * as h from '@api-ts/io-ts-http';

/**
* Enum with @deprecated tags - should generate x-enumsDeprecated
*/
export const StatusWithDeprecated = t.keyof(
{
/**
* @description Transaction is waiting for approval from authorized users
*/
pendingApproval: 1,
/**
* @description Transaction was canceled by the user
* @deprecated
*/
canceled: 1,
/**
* @description Transaction was rejected by approvers
* @deprecated
*/
rejected: 1,
},
'StatusWithDeprecated',
);

/**
* Enum with only @deprecated tags - should generate x-enumsDeprecated
*/
export const StatusOnlyDeprecated = t.keyof(
{
/** @deprecated */
old: 1,
current: 1,
/** @deprecated */
legacy: 1,
},
'StatusOnlyDeprecated',
);

/**
* Route to test enum deprecated scenarios
*
* @operationId api.v1.enumDeprecatedScenarios
* @tag Test Routes
*/
export const route = h.httpRoute({
path: '/enum-deprecated',
method: 'GET',
request: h.httpRequest({
query: {
withDeprecated: StatusWithDeprecated,
onlyDeprecated: StatusOnlyDeprecated,
},
}),
response: {
200: {
result: t.string
}
},
});
`;

testCase(
'enum deprecated scenarios - @deprecated tags with and without @description',
ROUTE_WITH_ENUM_DEPRECATED,
{
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0',
},
paths: {
'/enum-deprecated': {
get: {
summary: 'Route to test enum deprecated scenarios',
operationId: 'api.v1.enumDeprecatedScenarios',
tags: ['Test Routes'],
parameters: [
{
name: 'withDeprecated',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/StatusWithDeprecated',
},
},
{
name: 'onlyDeprecated',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/StatusOnlyDeprecated',
},
},
],
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
result: {
type: 'string',
},
},
required: ['result'],
},
},
},
},
},
},
},
},
components: {
schemas: {
StatusWithDeprecated: {
title: 'StatusWithDeprecated',
description: 'Enum with @deprecated tags - should generate x-enumsDeprecated',
type: 'string',
enum: ['pendingApproval', 'canceled', 'rejected'],
'x-enumDescriptions': {
pendingApproval:
'Transaction is waiting for approval from authorized users',
canceled: 'Transaction was canceled by the user',
rejected: 'Transaction was rejected by approvers',
},
'x-enumsDeprecated': ['canceled', 'rejected'],
},
StatusOnlyDeprecated: {
title: 'StatusOnlyDeprecated',
description:
'Enum with only @deprecated tags - should generate x-enumsDeprecated',
type: 'string',
enum: ['old', 'current', 'legacy'],
'x-enumsDeprecated': ['old', 'legacy'],
},
},
},
},
);