Skip to content
Merged
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
10 changes: 7 additions & 3 deletions packages/openapi-generator/src/knownImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as E from 'fp-ts/Either';

import { isPrimitive, type Schema } from './ir';
import { errorLeft } from './error';
import { parseCommentBlock } from './jsdoc';

export type DerefFn = (ref: Schema) => E.Either<string, Schema>;
export type KnownCodec = (
Expand Down Expand Up @@ -132,9 +133,12 @@ export const KNOWN_IMPORTS: KnownImports = {

for (const prop of enumValues) {
const propertySchema = arg.properties[prop];
if (propertySchema?.comment?.description) {
enumDescriptions[prop] = propertySchema.comment.description;
hasDescriptions = true;
if (propertySchema?.comment) {
const jsdoc = parseCommentBlock(propertySchema.comment);
if (jsdoc.tags?.description) {
enumDescriptions[prop] = jsdoc.tags.description;
hasDescriptions = true;
}
}
}

Expand Down
267 changes: 121 additions & 146 deletions packages/openapi-generator/test/openapi/comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1441,179 +1441,98 @@ testCase(
},
);

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

/**
* Transaction Request State Enum with individual descriptions
* Enum with @description tags - should generate x-enumDescriptions
*/
export const TransactionRequestState = t.keyof(
export const StatusWithDescriptions = t.keyof(
{
/** Transaction is waiting for approval from authorized users */
/**
* @description Transaction is waiting for approval from authorized users
*/
pendingApproval: 1,
/** Transaction was canceled by the user */
/**
* @description Transaction was canceled by the user
*/
canceled: 1,
/** Transaction was rejected by approvers */
/**
* @description Transaction was rejected by approvers
*/
rejected: 1,
/** Transaction has been initialized but not yet processed */
initialized: 1,
/** Transaction is ready to be delivered */
pendingDelivery: 1,
/** Transaction has been successfully delivered */
delivered: 1,
},
'TransactionRequestState',
'StatusWithDescriptions',
);

/**
* Route to test individual enum variant descriptions
*
* @operationId api.v1.enumVariantDescriptions
* @tag Test Routes
* Simple enum without any descriptions - should use standard format
*/
export const route = h.httpRoute({
path: '/transactions',
method: 'GET',
request: h.httpRequest({
query: {
states: t.array(TransactionRequestState),
},
}),
response: {
200: {
result: t.string
}
export const SimpleEnum = t.keyof(
{
value1: 1,
value2: 1,
value3: 1,
},
});
`;
'SimpleEnum',
);

testCase(
'individual enum variant descriptions use x-enumDescriptions extension',
ROUTE_WITH_INDIVIDUAL_ENUM_DESCRIPTIONS,
/**
* Enum with comments but no @description tags - should NOT generate x-enumDescriptions
*/
export const StatusWithComments = t.keyof(
{
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0',
},
paths: {
'/transactions': {
get: {
summary: 'Route to test individual enum variant descriptions',
operationId: 'api.v1.enumVariantDescriptions',
tags: ['Test Routes'],
parameters: [
{
name: 'states',
in: 'query',
required: true,
schema: {
type: 'array',
items: {
type: 'string',
enum: [
'pendingApproval',
'canceled',
'rejected',
'initialized',
'pendingDelivery',
'delivered',
],
'x-enumDescriptions': {
pendingApproval:
'Transaction is waiting for approval from authorized users',
canceled: 'Transaction was canceled by the user',
rejected: 'Transaction was rejected by approvers',
initialized:
'Transaction has been initialized but not yet processed',
pendingDelivery: 'Transaction is ready to be delivered',
delivered: 'Transaction has been successfully delivered',
},
description:
'Transaction Request State Enum with individual descriptions',
},
},
},
],
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
result: {
type: 'string',
},
},
required: ['result'],
},
},
},
},
},
},
},
},
components: {
schemas: {
TransactionRequestState: {
title: 'TransactionRequestState',
description: 'Transaction Request State Enum with individual descriptions',
type: 'string',
enum: [
'pendingApproval',
'canceled',
'rejected',
'initialized',
'pendingDelivery',
'delivered',
],
'x-enumDescriptions': {
pendingApproval:
'Transaction is waiting for approval from authorized users',
canceled: 'Transaction was canceled by the user',
rejected: 'Transaction was rejected by approvers',
initialized: 'Transaction has been initialized but not yet processed',
pendingDelivery: 'Transaction is ready to be delivered',
delivered: 'Transaction has been successfully delivered',
},
},
},
},
/** processing = a case has been picked up by the Trust Committee Email Worker, and is being...processed */
processing: 1,
/** approved status */
approved: 1,
denied: 1,
},
'StatusWithComments',
);

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

/**
* Simple enum without individual descriptions
* Enum with mixed comment types - only @description tags should be used
*/
export const SimpleEnum = t.keyof(
export const MixedCommentStatus = t.keyof(
{
value1: 1,
value2: 1,
value3: 1,
/**
* This is just an internal comment about pending status
*/
pending: 1,
/**
* processing = a case has been picked up by the Trust Committee Email Worker, and is being...processed
* @description Transaction is currently being processed by the system
*/
processing: 1,
/** approved by the team after review */
approved: 1,
/**
* @description Transaction was rejected due to validation failures
*/
rejected: 1,
/** denied without much explanation */
denied: 1,
},
'SimpleEnum',
'MixedCommentStatus',
);

/**
* Route to test enum without individual descriptions
* Route to test all enum description scenarios
*
* @operationId api.v1.simpleEnum
* @operationId api.v1.enumDescriptionScenarios
* @tag Test Routes
*/
export const route = h.httpRoute({
path: '/simple',
path: '/enum-scenarios',
method: 'GET',
request: h.httpRequest({
query: {
value: SimpleEnum,
withDescriptions: StatusWithDescriptions,
simple: SimpleEnum,
withComments: StatusWithComments,
mixed: MixedCommentStatus,
},
}),
response: {
Expand All @@ -1625,29 +1544,53 @@ export const route = h.httpRoute({
`;

testCase(
'enum without individual descriptions uses standard enum format',
ROUTE_WITH_ENUM_WITHOUT_DESCRIPTIONS,
'enum description scenarios - @description tags vs regular comments vs mixed',
ROUTE_WITH_ENUM_DESCRIPTIONS,
{
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0',
},
paths: {
'/simple': {
'/enum-scenarios': {
get: {
summary: 'Route to test enum without individual descriptions',
operationId: 'api.v1.simpleEnum',
summary: 'Route to test all enum description scenarios',
operationId: 'api.v1.enumDescriptionScenarios',
tags: ['Test Routes'],
parameters: [
{
name: 'value',
name: 'withDescriptions',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/StatusWithDescriptions',
},
},
{
name: 'simple',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/SimpleEnum',
},
},
{
name: 'withComments',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/StatusWithComments',
},
},
{
name: 'mixed',
in: 'query',
required: true,
schema: {
$ref: '#/components/schemas/MixedCommentStatus',
},
},
],
responses: {
200: {
Expand All @@ -1672,11 +1615,43 @@ testCase(
},
components: {
schemas: {
StatusWithDescriptions: {
title: 'StatusWithDescriptions',
description:
'Enum with @description tags - should generate x-enumDescriptions',
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',
},
},
SimpleEnum: {
title: 'SimpleEnum',
type: 'string',
enum: ['value1', 'value2', 'value3'],
description: 'Simple enum without individual descriptions',
description:
'Simple enum without any descriptions - should use standard format',
},
StatusWithComments: {
title: 'StatusWithComments',
type: 'string',
enum: ['processing', 'approved', 'denied'],
description:
'Enum with comments but no @description tags - should NOT generate x-enumDescriptions',
},
MixedCommentStatus: {
title: 'MixedCommentStatus',
type: 'string',
enum: ['pending', 'processing', 'approved', 'rejected', 'denied'],
'x-enumDescriptions': {
processing: 'Transaction is currently being processed by the system',
rejected: 'Transaction was rejected due to validation failures',
},
description:
'Enum with mixed comment types - only @description tags should be used',
},
},
},
Expand Down
Loading