Skip to content

Commit 0d89db6

Browse files
committed
🔧 fix: unfold ref to schema
1 parent f2b62fa commit 0d89db6

File tree

5 files changed

+91
-46
lines changed

5 files changed

+91
-46
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.2.1 - 19 Feb 2024
2+
Bug fix:
3+
- [elysia#1063](https://github.com/elysiajs/elysia/issues/1063) Using t.Ref as response schema results in invalid OpenAPI specification
4+
- handle unfold recursive Ref to schema
15

26
# 1.2.0-rc.0 - 23 Dec 2024
37
Change:

example/index.ts

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,56 @@
1-
import { Elysia } from 'elysia'
1+
import { Elysia, t } from 'elysia'
22
import { swagger } from '../src/index'
33

4+
const schema = t.Object({
5+
test: t.Literal('hello')
6+
})
7+
48
const app = new Elysia()
5-
.use(
6-
swagger({
7-
provider: 'scalar',
8-
documentation: {
9-
info: {
10-
title: 'Elysia Scalar',
11-
version: '0.8.1'
12-
},
13-
tags: [
14-
{
15-
name: 'Test',
16-
description: 'Hello'
17-
}
18-
],
19-
components: {
20-
schemas: {
21-
User: {
22-
description: 'string'
23-
}
24-
},
25-
securitySchemes: {
26-
JwtAuth: {
27-
type: 'http',
28-
scheme: 'bearer',
29-
bearerFormat: 'JWT',
30-
description: 'Enter JWT Bearer token **_only_**'
31-
}
32-
}
33-
}
34-
},
35-
swaggerOptions: {
36-
persistAuthorization: true
37-
}
38-
})
39-
)
40-
.get('/id/:id?', 'a')
41-
.listen(3000)
9+
.use(
10+
swagger({
11+
provider: 'scalar',
12+
documentation: {
13+
info: {
14+
title: 'Elysia Scalar',
15+
version: '0.8.1'
16+
},
17+
tags: [
18+
{
19+
name: 'Test',
20+
description: 'Hello'
21+
}
22+
],
23+
components: {
24+
schemas: {
25+
User: {
26+
description: 'string'
27+
}
28+
},
29+
securitySchemes: {
30+
JwtAuth: {
31+
type: 'http',
32+
scheme: 'bearer',
33+
bearerFormat: 'JWT',
34+
description: 'Enter JWT Bearer token **_only_**'
35+
}
36+
}
37+
}
38+
},
39+
swaggerOptions: {
40+
persistAuthorization: true
41+
}
42+
})
43+
)
44+
.model({ schema })
45+
.get(
46+
'/',
47+
() => {
48+
test: 'hello'
49+
},
50+
{
51+
response: t.Object({
52+
a: t.Ref('schema')
53+
})
54+
}
55+
)
56+
.listen(3000)

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elysiajs/swagger",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "Plugin for Elysia to auto-generate Swagger page",
55
"author": {
66
"name": "saltyAom",
@@ -73,4 +73,4 @@
7373
"openapi-types": "^12.1.3",
7474
"pathe": "^1.1.2"
7575
}
76-
}
76+
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export const swagger = async <Path extends string = '/swagger'>(
117117
const ALLOWED_METHODS = ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'TRACE']
118118
totalRoutes = routes.length
119119

120-
routes.forEach((route: InternalRoute) => {
120+
for(const route of routes) {
121121
if (route.hooks?.detail?.hide === true) return
122122
// TODO: route.hooks?.detail?.hide !== false add ability to hide: false to prevent excluding
123123
if (excludeMethods.includes(route.method)) return
@@ -147,7 +147,7 @@ export const swagger = async <Path extends string = '/swagger'>(
147147
models: app.definitions?.type,
148148
contentType: route.hooks.type
149149
})
150-
})
150+
}
151151
}
152152

153153
return {

src/utils.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/ban-ts-comment */
22
/* eslint-disable @typescript-eslint/no-unused-vars */
33
import { normalize } from 'pathe'
4-
import type { HTTPMethod, LocalHook } from 'elysia'
4+
import { replaceSchemaType, t, type HTTPMethod, type LocalHook } from 'elysia'
55

66
import { Kind, type TSchema } from '@sinclair/typebox'
77
import type { OpenAPIV3 } from 'openapi-types'
@@ -70,15 +70,39 @@ const mapTypesResponse = (
7070
const responses: Record<string, OpenAPIV3.MediaTypeObject> = {}
7171

7272
for (const type of types) {
73-
// console.log(schema)
74-
7573
responses[type] = {
7674
schema:
7775
typeof schema === 'string'
7876
? {
7977
$ref: `#/components/schemas/${schema}`
8078
}
81-
: { ...(schema as any) }
79+
: '$ref' in schema &&
80+
Kind in schema &&
81+
schema[Kind] === 'Ref'
82+
? {
83+
...schema,
84+
$ref: `#/components/schemas/${schema.$ref}`
85+
}
86+
: replaceSchemaType(
87+
{ ...(schema as any) },
88+
{
89+
from: t.Ref(''),
90+
// @ts-expect-error
91+
to: ({ $ref, ...options }) => {
92+
if (
93+
!$ref.startsWith(
94+
'#/components/schemas/'
95+
)
96+
)
97+
return t.Ref(
98+
`#/components/schemas/${$ref}`,
99+
options
100+
)
101+
102+
return t.Ref($ref, options)
103+
}
104+
}
105+
)
82106
}
83107
}
84108

@@ -154,6 +178,7 @@ export const registerSchemaPath = ({
154178
required,
155179
additionalProperties,
156180
patternProperties,
181+
$ref,
157182
...rest
158183
} = responseSchema as typeof responseSchema & {
159184
type: string
@@ -246,6 +271,7 @@ export const registerSchemaPath = ({
246271
type,
247272
properties,
248273
required,
274+
$ref,
249275
additionalProperties: _1,
250276
patternProperties: _2,
251277
...rest

0 commit comments

Comments
 (0)