2024-06-14 23:21:44 +08:00
|
|
|
import type { Env, Hono, ToSchema } from 'hono'
|
|
|
|
import { assertType, describe, expectTypeOf, it } from 'vitest'
|
2023-12-05 17:15:06 +08:00
|
|
|
import { OpenAPIHono, createRoute, z } from '../src/index'
|
2024-07-07 11:42:18 +08:00
|
|
|
import type { ExtractSchema } from 'hono/types'
|
|
|
|
import type { Equal, Expect } from 'hono/utils/types'
|
2023-12-05 17:15:06 +08:00
|
|
|
|
|
|
|
describe('Types', () => {
|
|
|
|
const RequestSchema = z.object({
|
|
|
|
id: z.number().openapi({}),
|
|
|
|
title: z.string().openapi({}),
|
|
|
|
})
|
|
|
|
|
|
|
|
const PostSchema = z
|
|
|
|
.object({
|
|
|
|
id: z.number().openapi({}),
|
|
|
|
message: z.string().openapi({}),
|
|
|
|
})
|
|
|
|
.openapi('Post')
|
|
|
|
|
|
|
|
const route = createRoute({
|
|
|
|
method: 'post',
|
|
|
|
path: '/posts',
|
|
|
|
request: {
|
|
|
|
body: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: RequestSchema,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
responses: {
|
|
|
|
200: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: PostSchema,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: 'Post a post',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const app = new OpenAPIHono()
|
|
|
|
|
|
|
|
const appRoutes = app.openapi(route, (c) => {
|
|
|
|
const data = c.req.valid('json')
|
|
|
|
assertType<number>(data.id)
|
2024-03-22 16:23:53 +08:00
|
|
|
return c.json({
|
2023-12-05 17:15:06 +08:00
|
|
|
id: data.id,
|
|
|
|
message: 'Success',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should return correct types', () => {
|
|
|
|
type H = Hono<
|
|
|
|
Env,
|
|
|
|
ToSchema<
|
|
|
|
'post',
|
|
|
|
'/posts',
|
|
|
|
{
|
|
|
|
json: {
|
|
|
|
title: string
|
|
|
|
id: number
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: number
|
|
|
|
message: string
|
|
|
|
}
|
|
|
|
>,
|
|
|
|
'/'
|
|
|
|
>
|
|
|
|
expectTypeOf(appRoutes).toMatchTypeOf<H>()
|
2024-04-07 18:18:56 +08:00
|
|
|
})
|
2023-12-05 17:15:06 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('Input types', () => {
|
|
|
|
const ParamsSchema = z.object({
|
2023-12-13 16:31:25 +08:00
|
|
|
id: z
|
|
|
|
.string()
|
|
|
|
.transform(Number)
|
|
|
|
.openapi({
|
|
|
|
param: {
|
|
|
|
name: 'id',
|
|
|
|
in: 'path',
|
|
|
|
},
|
2024-04-07 18:18:56 +08:00
|
|
|
example: '123',
|
2023-12-13 16:31:25 +08:00
|
|
|
}),
|
2023-12-05 17:15:06 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
const QuerySchema = z.object({
|
2023-12-13 16:31:25 +08:00
|
|
|
age: z
|
|
|
|
.string()
|
|
|
|
.transform(Number)
|
|
|
|
.openapi({
|
|
|
|
param: {
|
|
|
|
name: 'age',
|
|
|
|
in: 'query',
|
|
|
|
},
|
2024-04-07 18:18:56 +08:00
|
|
|
example: '42',
|
2023-12-13 16:31:25 +08:00
|
|
|
}),
|
2023-12-05 17:15:06 +08:00
|
|
|
})
|
|
|
|
|
2023-12-13 16:31:25 +08:00
|
|
|
const BodySchema = z
|
|
|
|
.object({
|
|
|
|
sex: z.enum(['male', 'female']).openapi({}),
|
|
|
|
})
|
|
|
|
.openapi('User')
|
2023-12-05 17:15:06 +08:00
|
|
|
|
|
|
|
const UserSchema = z
|
|
|
|
.object({
|
|
|
|
id: z.number().openapi({
|
|
|
|
example: 123,
|
|
|
|
}),
|
|
|
|
name: z.string().openapi({
|
|
|
|
example: 'John Doe',
|
|
|
|
}),
|
|
|
|
age: z.number().openapi({
|
|
|
|
example: 42,
|
|
|
|
}),
|
|
|
|
sex: z.enum(['male', 'female']).openapi({
|
|
|
|
example: 'male',
|
2023-12-13 16:31:25 +08:00
|
|
|
}),
|
2023-12-05 17:15:06 +08:00
|
|
|
})
|
|
|
|
.openapi('User')
|
|
|
|
|
|
|
|
const route = createRoute({
|
|
|
|
method: 'patch',
|
|
|
|
path: '/users/{id}',
|
|
|
|
request: {
|
|
|
|
params: ParamsSchema,
|
|
|
|
query: QuerySchema,
|
|
|
|
body: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: BodySchema,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
responses: {
|
|
|
|
200: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: UserSchema,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: 'Update a user',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
it('Should return correct types', () => {
|
|
|
|
const app = new OpenAPIHono()
|
|
|
|
|
|
|
|
app.openapi(route, (c) => {
|
|
|
|
const { id } = c.req.valid('param')
|
|
|
|
assertType<number>(id)
|
|
|
|
|
|
|
|
const { age } = c.req.valid('query')
|
|
|
|
assertType<number>(age)
|
|
|
|
|
|
|
|
const { sex } = c.req.valid('json')
|
|
|
|
assertType<'male' | 'female'>(sex)
|
|
|
|
|
2024-04-07 18:18:56 +08:00
|
|
|
return c.json({
|
2023-12-05 17:15:06 +08:00
|
|
|
id,
|
|
|
|
age,
|
|
|
|
sex,
|
|
|
|
name: 'Ultra-man',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2024-06-14 23:21:44 +08:00
|
|
|
|
|
|
|
describe('Response schema includes a Date type', () => {
|
|
|
|
it('Should not throw a type error', () => {
|
|
|
|
new OpenAPIHono().openapi(
|
|
|
|
createRoute({
|
|
|
|
method: 'get',
|
|
|
|
path: '/example',
|
|
|
|
responses: {
|
|
|
|
200: {
|
|
|
|
content: {
|
|
|
|
'application/json': {
|
|
|
|
schema: z.object({
|
|
|
|
updatedAt: z.date(),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: '',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
async (ctx) => {
|
|
|
|
// Don't throw an error:
|
|
|
|
return ctx.json({ updatedAt: new Date() }, 200)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2024-07-07 11:42:18 +08:00
|
|
|
|
|
|
|
describe('coerce', () => {
|
|
|
|
it('Should not throw type errors', () => {
|
|
|
|
const routes = new OpenAPIHono().openapi(
|
|
|
|
createRoute({
|
|
|
|
method: 'get',
|
|
|
|
path: '/api/users/{id}',
|
|
|
|
request: {
|
|
|
|
params: z.object({
|
|
|
|
id: z.coerce.number().openapi({ description: 'userId', example: 1 }),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
responses: {
|
|
|
|
200: {
|
|
|
|
description: 'Get a user',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
(c) => {
|
|
|
|
const { id } = c.req.valid('param')
|
|
|
|
assertType<number>(id)
|
|
|
|
return c.json({ id })
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
type Actual = ExtractSchema<typeof routes>['/api/users/:id']['$get']['input']
|
|
|
|
type Expected = {
|
|
|
|
param: {
|
2024-08-10 15:01:19 +08:00
|
|
|
id: string
|
2024-07-07 11:42:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
type verify = Expect<Equal<Expected, Actual>>
|
|
|
|
})
|
|
|
|
})
|