Skip to content

Commit 303be10

Browse files
committed
🧹 chore: refactor enumToOpenAPI
1 parent 3a318dd commit 303be10

File tree

3 files changed

+150
-172
lines changed

3 files changed

+150
-172
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.4.10 - 22 Sep 2025
2+
Bug fix:
3+
- [#226](https://github.com/elysiajs/elysia-openapi/issues/266) accept operationId
4+
15
# 1.4.9 - 21 Sep 2025
26
Improvement:
37
- type gen: match special, ad non-english character

src/openapi.ts

Lines changed: 77 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { t, type AnyElysia, type TSchema, type InputSchema } from 'elysia'
22
import type { HookContainer, StandardSchemaV1Like } from 'elysia/types'
33

44
import type { OpenAPIV3 } from 'openapi-types'
5-
import { Kind, type TProperties } from '@sinclair/typebox'
5+
import { Kind, TAnySchema, type TProperties } from '@sinclair/typebox'
66

77
import type {
88
AdditionalReference,
99
AdditionalReferences,
1010
ElysiaOpenAPIConfig,
1111
MapJsonSchema
1212
} from './types'
13+
import { defineConfig } from 'tsup'
1314

1415
export const capitalize = (word: string) =>
1516
word.charAt(0).toUpperCase() + word.slice(1)
@@ -121,7 +122,7 @@ const unwrapReference = <T extends OpenAPIV3.SchemaObject | undefined>(
121122
const name = ref.slice(ref.lastIndexOf('/') + 1)
122123
if (ref && definitions[name]) schema = definitions[name] as T
123124

124-
return schema as any
125+
return enumToOpenApi(schema) as any
125126
}
126127

127128
export const unwrapSchema = (
@@ -131,15 +132,15 @@ export const unwrapSchema = (
131132
if (!schema) return
132133

133134
if (typeof schema === 'string') schema = toRef(schema)
134-
if (Kind in schema) return schema
135+
if (Kind in schema) return enumToOpenApi(schema)
135136

136137
if (Kind in schema || !schema?.['~standard']) return
137138

138139
// @ts-ignore
139140
const vendor = schema['~standard'].vendor
140141

141142
if (mapJsonSchema?.[vendor] && typeof mapJsonSchema[vendor] === 'function')
142-
return mapJsonSchema[vendor](schema)
143+
return enumToOpenApi(mapJsonSchema[vendor](schema))
143144

144145
switch (vendor) {
145146
case 'zod':
@@ -197,52 +198,62 @@ export const unwrapSchema = (
197198

198199
if (vendor === 'arktype')
199200
// @ts-ignore
200-
return schema?.toJsonSchema?.()
201+
return enumToOpenApi(schema?.toJsonSchema?.())
201202

202203
// @ts-ignore
203-
return schema.toJSONSchema?.() ?? schema?.toJsonSchema?.()
204+
return enumToOpenApi(schema.toJSONSchema?.() ?? schema?.toJsonSchema?.())
204205
}
205206

206-
export const convertEnumToOpenApi = (schema: any): any => {
207-
if (!schema || typeof schema !== 'object') return schema
208-
209-
if (
210-
schema[Kind] === 'Union' &&
211-
schema.anyOf &&
212-
Array.isArray(schema.anyOf) &&
213-
schema.anyOf.length > 0 &&
214-
schema.anyOf.every(
215-
(item: any) =>
216-
item && typeof item === 'object' && item.const !== undefined
217-
)
218-
) {
219-
const enumValues = schema.anyOf.map((item: any) => item.const)
207+
export const enumToOpenApi = <
208+
T extends
209+
| TAnySchema
210+
| OpenAPIV3.SchemaObject
211+
| OpenAPIV3.ReferenceObject
212+
| undefined
213+
>(
214+
_schema: T
215+
): T => {
216+
if (!_schema || typeof _schema !== 'object') return _schema
220217

221-
return {
222-
type: 'string',
223-
enum: enumValues
224-
}
218+
if (Kind in _schema) {
219+
const schema = _schema as TAnySchema
220+
221+
if (
222+
schema[Kind] === 'Union' &&
223+
schema.anyOf &&
224+
Array.isArray(schema.anyOf) &&
225+
schema.anyOf.length > 0 &&
226+
schema.anyOf.every(
227+
(item) =>
228+
item && typeof item === 'object' && item.const !== undefined
229+
)
230+
)
231+
return {
232+
type: 'string',
233+
enum: schema.anyOf.map((item) => item.const)
234+
} as any
225235
}
226236

237+
const schema = _schema as OpenAPIV3.SchemaObject
238+
227239
if (schema.type === 'object' && schema.properties) {
228-
const convertedProperties: any = {}
229-
for (const [key, value] of Object.entries(schema.properties)) {
230-
convertedProperties[key] = convertEnumToOpenApi(value)
231-
}
240+
const properties: Record<string, unknown> = {}
241+
for (const [key, value] of Object.entries(schema.properties))
242+
properties[key] = enumToOpenApi(value)
243+
232244
return {
233245
...schema,
234-
properties: convertedProperties
235-
}
246+
properties
247+
} as T
236248
}
237249

238-
if (schema.type === 'array' && schema.items) {
250+
if (schema.type === 'array' && schema.items)
239251
return {
240252
...schema,
241-
items: convertEnumToOpenApi(schema.items)
242-
}
243-
}
253+
items: enumToOpenApi(schema.items)
254+
} as T
244255

245-
return schema
256+
return schema as T
246257
}
247258

248259
/**
@@ -337,7 +348,6 @@ export function toOpenAPISchema(
337348
(hooks.response as TSchema).$ref ||
338349
(hooks.response as any)['~standard']
339350
)
340-
// @ts-ignore
341351
hooks.response = {
342352
200: hooks.response as any
343353
}
@@ -384,25 +394,16 @@ export function toOpenAPISchema(
384394
definitions
385395
)
386396

387-
if (params && params.type === 'object' && params.properties) {
388-
const convertedProperties: any = {}
397+
if (params && params.type === 'object' && params.properties)
389398
for (const [paramName, paramSchema] of Object.entries(
390399
params.properties
391-
)) {
392-
convertedProperties[paramName] =
393-
convertEnumToOpenApi(paramSchema)
394-
}
395-
396-
for (const [paramName, paramSchema] of Object.entries(
397-
convertedProperties
398400
))
399401
parameters.push({
400402
name: paramName,
401403
in: 'path',
402404
required: true, // Path parameters are always required
403405
schema: paramSchema
404406
})
405-
}
406407
}
407408

408409
// Handle query parameters
@@ -413,17 +414,9 @@ export function toOpenAPISchema(
413414
)
414415

415416
if (query && query.type === 'object' && query.properties) {
416-
const convertedProperties: any = {}
417-
for (const [queryName, querySchema] of Object.entries(
418-
query.properties
419-
)) {
420-
convertedProperties[queryName] =
421-
convertEnumToOpenApi(querySchema)
422-
}
423-
424417
const required = query.required || []
425418
for (const [queryName, querySchema] of Object.entries(
426-
convertedProperties
419+
query.properties
427420
))
428421
parameters.push({
429422
name: queryName,
@@ -442,17 +435,9 @@ export function toOpenAPISchema(
442435
)
443436

444437
if (headers && headers.type === 'object' && headers.properties) {
445-
const convertedProperties: any = {}
446-
for (const [headerName, headerSchema] of Object.entries(
447-
headers.properties
448-
)) {
449-
convertedProperties[headerName] =
450-
convertEnumToOpenApi(headerSchema)
451-
}
452-
453438
const required = headers.required || []
454439
for (const [headerName, headerSchema] of Object.entries(
455-
convertedProperties
440+
headers.properties
456441
))
457442
parameters.push({
458443
name: headerName,
@@ -471,17 +456,9 @@ export function toOpenAPISchema(
471456
)
472457

473458
if (cookie && cookie.type === 'object' && cookie.properties) {
474-
const convertedProperties: any = {}
475-
for (const [cookieName, cookieSchema] of Object.entries(
476-
cookie.properties
477-
)) {
478-
convertedProperties[cookieName] =
479-
convertEnumToOpenApi(cookieSchema)
480-
}
481-
482459
const required = cookie.required || []
483460
for (const [cookieName, cookieSchema] of Object.entries(
484-
convertedProperties
461+
cookie.properties
485462
))
486463
parameters.push({
487464
name: cookieName,
@@ -500,11 +477,11 @@ export function toOpenAPISchema(
500477
const body = unwrapSchema(hooks.body, vendors)
501478

502479
if (body) {
503-
const convertedBody = convertEnumToOpenApi(body)
504-
505480
// @ts-ignore
506-
const { type: _type, description, $ref, ...options } = convertedBody
507-
const type = _type as string | undefined
481+
const { type, description, $ref, ...options } = unwrapReference(
482+
body,
483+
definitions
484+
)
508485

509486
// @ts-ignore
510487
if (hooks.parse) {
@@ -522,24 +499,26 @@ export function toOpenAPISchema(
522499
switch (parser.fn) {
523500
case 'text':
524501
case 'text/plain':
525-
content['text/plain'] = { schema: convertedBody }
502+
content['text/plain'] = { schema: body }
526503
continue
527504

528505
case 'urlencoded':
529506
case 'application/x-www-form-urlencoded':
530507
content['application/x-www-form-urlencoded'] = {
531-
schema: convertedBody
508+
schema: body
532509
}
533510
continue
534511

535512
case 'json':
536513
case 'application/json':
537-
content['application/json'] = { schema: convertedBody }
514+
content['application/json'] = { schema: body }
538515
continue
539516

540517
case 'formdata':
541518
case 'multipart/form-data':
542-
content['multipart/form-data'] = { schema: convertedBody }
519+
content['multipart/form-data'] = {
520+
schema: body
521+
}
543522
continue
544523
}
545524
}
@@ -558,17 +537,19 @@ export function toOpenAPISchema(
558537
type === 'integer' ||
559538
type === 'boolean'
560539
? {
561-
'text/plain': convertedBody
540+
'text/plain': {
541+
schema: body
542+
}
562543
}
563544
: {
564545
'application/json': {
565-
schema: convertedBody
546+
schema: body
566547
},
567548
'application/x-www-form-urlencoded': {
568-
schema: convertedBody
549+
schema: body
569550
},
570551
'multipart/form-data': {
571-
schema: convertedBody
552+
schema: body
572553
}
573554
},
574555
required: true
@@ -593,10 +574,9 @@ export function toOpenAPISchema(
593574

594575
if (!response) continue
595576

596-
const convertedResponse = convertEnumToOpenApi(response)
597577
// @ts-ignore Must exclude $ref from root options
598-
const { type: _type, description, $ref, ...options } = convertedResponse
599-
const type = _type as string | undefined
578+
const { type, description, $ref, ...options } =
579+
unwrapReference(response, definitions)
600580

601581
operation.responses[status] = {
602582
description:
@@ -605,19 +585,19 @@ export function toOpenAPISchema(
605585
type === 'void' ||
606586
type === 'null' ||
607587
type === 'undefined'
608-
? (convertedResponse as any)
588+
? ({ type, description } as any)
609589
: type === 'string' ||
610590
type === 'number' ||
611591
type === 'integer' ||
612592
type === 'boolean'
613593
? {
614594
'text/plain': {
615-
schema: convertedResponse
595+
schema: response
616596
}
617597
}
618598
: {
619599
'application/json': {
620-
schema: convertedResponse
600+
schema: response
621601
}
622602
}
623603
}
@@ -626,14 +606,12 @@ export function toOpenAPISchema(
626606
const response = unwrapSchema(hooks.response as any, vendors)
627607

628608
if (response) {
629-
const convertedResponse = convertEnumToOpenApi(response)
630-
631609
// @ts-ignore
632610
const {
633611
type: _type,
634612
description,
635613
...options
636-
} = convertedResponse
614+
} = unwrapReference(response, definitions)
637615
const type = _type as string | undefined
638616

639617
// It's a single schema, default to 200
@@ -643,19 +621,19 @@ export function toOpenAPISchema(
643621
type === 'void' ||
644622
type === 'null' ||
645623
type === 'undefined'
646-
? (convertedResponse as any)
624+
? ({ type, description } as any)
647625
: type === 'string' ||
648626
type === 'number' ||
649627
type === 'integer' ||
650628
type === 'boolean'
651629
? {
652630
'text/plain': {
653-
schema: convertedResponse
631+
schema: response
654632
}
655633
}
656634
: {
657635
'application/json': {
658-
schema: convertedResponse
636+
schema: response
659637
}
660638
}
661639
}
@@ -664,7 +642,8 @@ export function toOpenAPISchema(
664642
}
665643

666644
for (let path of getPossiblePath(route.path)) {
667-
const operationId = toOperationId(route.method, path)
645+
const operationId =
646+
hooks.detail?.operationId ?? toOperationId(route.method, path)
668647

669648
path = path.replace(/:([^/]+)/g, '{$1}')
670649

0 commit comments

Comments
 (0)