Skip to content

Commit 32c451a

Browse files
committed
✨ feat: add enum conversion
1 parent 480f7f7 commit 32c451a

File tree

1 file changed

+101
-23
lines changed

1 file changed

+101
-23
lines changed

src/openapi.ts

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,48 @@ export const unwrapSchema = (
9797
return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()
9898
}
9999

100+
const convertEnumToOpenAPI = (schema: any): any => {
101+
if (!schema || typeof schema !== 'object') return schema
102+
103+
if (
104+
schema[Kind] === 'Union' &&
105+
schema.anyOf &&
106+
Array.isArray(schema.anyOf) &&
107+
schema.anyOf.length > 0 &&
108+
schema.anyOf.every(
109+
(item: any) =>
110+
item && typeof item === 'object' && item.const !== undefined
111+
)
112+
) {
113+
const enumValues = schema.anyOf.map((item: any) => item.const)
114+
115+
return {
116+
type: 'string',
117+
enum: enumValues
118+
}
119+
}
120+
121+
if (schema.type === 'object' && schema.properties) {
122+
const convertedProperties: any = {}
123+
for (const [key, value] of Object.entries(schema.properties)) {
124+
convertedProperties[key] = convertEnumToOpenAPI(value)
125+
}
126+
return {
127+
...schema,
128+
properties: convertedProperties
129+
}
130+
}
131+
132+
if (schema.type === 'array' && schema.items) {
133+
return {
134+
...schema,
135+
items: convertEnumToOpenAPI(schema.items)
136+
}
137+
}
138+
139+
return schema
140+
}
141+
100142
/**
101143
* Converts Elysia routes to OpenAPI 3.0.3 paths schema
102144
* @param routes Array of Elysia route objects
@@ -212,26 +254,43 @@ export function toOpenAPISchema(
212254
if (hooks.params) {
213255
const params = unwrapSchema(hooks.params, vendors)
214256

215-
if (params && params.type === 'object' && params.properties)
257+
if (params && params.type === 'object' && params.properties) {
258+
const convertedProperties: any = {}
216259
for (const [paramName, paramSchema] of Object.entries(
217260
params.properties
261+
)) {
262+
convertedProperties[paramName] =
263+
convertEnumToOpenAPI(paramSchema)
264+
}
265+
266+
for (const [paramName, paramSchema] of Object.entries(
267+
convertedProperties
218268
))
219269
parameters.push({
220270
name: paramName,
221271
in: 'path',
222272
required: true, // Path parameters are always required
223273
schema: paramSchema
224274
})
275+
}
225276
}
226277

227278
// Handle query parameters
228279
if (hooks.query) {
229280
let query = unwrapSchema(hooks.query, vendors)
230281

231282
if (query && query.type === 'object' && query.properties) {
232-
const required = query.required || []
283+
const convertedProperties: any = {}
233284
for (const [queryName, querySchema] of Object.entries(
234285
query.properties
286+
)) {
287+
convertedProperties[queryName] =
288+
convertEnumToOpenAPI(querySchema)
289+
}
290+
291+
const required = query.required || []
292+
for (const [queryName, querySchema] of Object.entries(
293+
convertedProperties
235294
))
236295
parameters.push({
237296
name: queryName,
@@ -247,9 +306,17 @@ export function toOpenAPISchema(
247306
const headers = unwrapSchema(hooks.query, vendors)
248307

249308
if (headers && headers.type === 'object' && headers.properties) {
250-
const required = headers.required || []
309+
const convertedProperties: any = {}
251310
for (const [headerName, headerSchema] of Object.entries(
252311
headers.properties
312+
)) {
313+
convertedProperties[headerName] =
314+
convertEnumToOpenAPI(headerSchema)
315+
}
316+
317+
const required = headers.required || []
318+
for (const [headerName, headerSchema] of Object.entries(
319+
convertedProperties
253320
))
254321
parameters.push({
255322
name: headerName,
@@ -265,9 +332,17 @@ export function toOpenAPISchema(
265332
const cookie = unwrapSchema(hooks.cookie, vendors)
266333

267334
if (cookie && cookie.type === 'object' && cookie.properties) {
268-
const required = cookie.required || []
335+
const convertedProperties: any = {}
269336
for (const [cookieName, cookieSchema] of Object.entries(
270337
cookie.properties
338+
)) {
339+
convertedProperties[cookieName] =
340+
convertEnumToOpenAPI(cookieSchema)
341+
}
342+
343+
const required = cookie.required || []
344+
for (const [cookieName, cookieSchema] of Object.entries(
345+
convertedProperties
271346
))
272347
parameters.push({
273348
name: cookieName,
@@ -286,8 +361,10 @@ export function toOpenAPISchema(
286361
const body = unwrapSchema(hooks.body, vendors)
287362

288363
if (body) {
364+
const convertedBody = convertEnumToOpenAPI(body)
365+
289366
// @ts-ignore
290-
const { type: _type, description, ...options } = body
367+
const { type: _type, description, ...options } = convertedBody
291368
const type = _type as string | undefined
292369

293370
// @ts-ignore
@@ -306,26 +383,24 @@ export function toOpenAPISchema(
306383
switch (parser.fn) {
307384
case 'text':
308385
case 'text/plain':
309-
content['text/plain'] = { schema: body }
386+
content['text/plain'] = { schema: convertedBody }
310387
continue
311388

312389
case 'urlencoded':
313390
case 'application/x-www-form-urlencoded':
314391
content['application/x-www-form-urlencoded'] = {
315-
schema: body
392+
schema: convertedBody
316393
}
317394
continue
318395

319396
case 'json':
320397
case 'application/json':
321-
content['application/json'] = { schema: body }
398+
content['application/json'] = { schema: convertedBody }
322399
continue
323400

324401
case 'formdata':
325402
case 'multipart/form-data':
326-
content['multipart/form-data'] = {
327-
schema: body
328-
}
403+
content['multipart/form-data'] = { schema: convertedBody }
329404
continue
330405
}
331406
}
@@ -344,17 +419,17 @@ export function toOpenAPISchema(
344419
type === 'integer' ||
345420
type === 'boolean'
346421
? {
347-
'text/plain': body
422+
'text/plain': convertedBody
348423
}
349424
: {
350425
'application/json': {
351-
schema: body
426+
schema: convertedBody
352427
},
353428
'application/x-www-form-urlencoded': {
354-
schema: body
429+
schema: convertedBody
355430
},
356431
'multipart/form-data': {
357-
schema: body
432+
schema: convertedBody
358433
}
359434
},
360435
required: true
@@ -377,8 +452,9 @@ export function toOpenAPISchema(
377452

378453
if (!response) continue
379454

455+
const convertedResponse = convertEnumToOpenAPI(response)
380456
// @ts-ignore Must exclude $ref from root options
381-
const { type: _type, description, ...options } = response
457+
const { type: _type, description, ...options } = convertedResponse
382458
const type = _type as string | undefined
383459

384460
operation.responses[status] = {
@@ -389,19 +465,19 @@ export function toOpenAPISchema(
389465
type === 'void' ||
390466
type === 'null' ||
391467
type === 'undefined'
392-
? (response as any)
468+
? (convertedResponse as any)
393469
: type === 'string' ||
394470
type === 'number' ||
395471
type === 'integer' ||
396472
type === 'boolean'
397473
? {
398474
'text/plain': {
399-
schema: response
475+
schema: convertedResponse
400476
}
401477
}
402478
: {
403479
'application/json': {
404-
schema: response
480+
schema: convertedResponse
405481
}
406482
}
407483
}
@@ -410,8 +486,10 @@ export function toOpenAPISchema(
410486
const response = unwrapSchema(hooks.response as any, vendors)
411487

412488
if (response) {
489+
const convertedResponse = convertEnumToOpenAPI(response)
490+
413491
// @ts-ignore
414-
const { type: _type, description, ...options } = response
492+
const { type: _type, description, ...options } = convertedResponse
415493
const type = _type as string | undefined
416494

417495
// It's a single schema, default to 200
@@ -421,19 +499,19 @@ export function toOpenAPISchema(
421499
type === 'void' ||
422500
type === 'null' ||
423501
type === 'undefined'
424-
? (response as any)
502+
? (convertedResponse as any)
425503
: type === 'string' ||
426504
type === 'number' ||
427505
type === 'integer' ||
428506
type === 'boolean'
429507
? {
430508
'text/plain': {
431-
schema: response
509+
schema: convertedResponse
432510
}
433511
}
434512
: {
435513
'application/json': {
436-
schema: response
514+
schema: convertedResponse
437515
}
438516
}
439517
}

0 commit comments

Comments
 (0)