honojs-middleware/packages/zod-openapi/test/index.test-d.ts

341 lines
7.2 KiB
TypeScript

import { assertType, describe, it } from 'vitest'
import { MiddlewareToHandlerType, OfHandlerType, OpenAPIHono, createRoute, z } from '../src/index'
import { createMiddleware } from 'hono/factory'
import type { ExtractSchema } from 'hono/types'
import type { Equal, Expect } from 'hono/utils/types'
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 QuerySchema = z.object({ order: z.enum(['asc', 'desc']) })
const route = createRoute({
method: 'post',
path: '/posts',
request: {
query: QuerySchema,
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)
const order = c.req.valid('query')
assertType<{ order: 'asc' | 'desc' }>(order)
return c.json({
id: data.id,
message: 'Success',
order,
})
})
it('Should return correct types', () => {
type Expected = {
'/posts': {
$post: {
input: {
query: {
order: 'asc' | 'desc'
}
} & {
json: {
title: string
id: number
}
}
output: {
id: number
message: string
}
outputFormat: 'json'
status: 200
}
}
}
type Actual = ExtractSchema<typeof appRoutes>
type verify = Expect<Equal<Expected, Actual>>
})
})
describe('Input types', () => {
const ParamsSchema = z.object({
id: z
.string()
.transform(Number)
.openapi({
param: {
name: 'id',
in: 'path',
},
example: '123',
}),
})
const QuerySchema = z.object({
age: z
.string()
.transform(Number)
.openapi({
param: {
name: 'age',
in: 'query',
},
example: '42',
}),
})
const BodySchema = z
.object({
sex: z.enum(['male', 'female']).openapi({}),
})
.openapi('User')
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',
}),
})
.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)
return c.json({
id,
age,
sex,
name: 'Ultra-man',
})
})
})
})
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)
}
)
})
})
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: {
id: number
}
}
type verify = Expect<Equal<Expected, Actual>>
})
})
describe('Middleware', () => {
it('Should merge Env', async () => {
const middlewareA = createMiddleware<{
Variables: { foo: string }
}>(async (c, next) => {
c.set('foo', 'abc')
next()
})
const middlewareB = createMiddleware<{
Variables: { bar: number }
}>(async (c, next) => {
c.set('bar', 321)
next()
})
type Example = MiddlewareToHandlerType<[typeof middlewareA, typeof middlewareB]>
type verify = Expect<
Equal<
OfHandlerType<Example>['env'],
{
Variables: { foo: string; bar: number }
}
>
>
})
it('Should infer Env from router middleware', async () => {
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
app.openapi(
createRoute({
method: 'get',
path: '/books',
middleware: [
createMiddleware<{
Variables: { foo: string }
}>((c, next) => {
c.set('foo', 'abc')
return next()
}),
createMiddleware<{
Variables: { bar: number }
}>((c, next) => {
c.set('bar', 321)
return next()
}),
] as const,
responses: {
200: {
description: 'response',
},
},
}),
(c) => {
c.var.foo
c.var.bar
c.var.too
type verifyFoo = Expect<Equal<typeof c.var.foo, string>>
type verifyBar = Expect<Equal<typeof c.var.bar, number>>
type verifyToo = Expect<Equal<typeof c.var.too, Symbol>>
return c.json({})
}
)
})
it('Should infer Env root when no middleware provided', async () => {
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
app.openapi(
createRoute({
method: 'get',
path: '/books',
middleware: undefined,
responses: {
200: {
description: 'response',
},
},
}),
(c) => {
c.var.too
type verify = Expect<Equal<typeof c.var.too, Symbol>>
return c.json({})
}
)
})
})