feat(zod-openapi): infer env from routeMiddleware (#807)
* feat(zod-openapi): infer env from routeMiddleware * chore(zod-openapi): add changeset for routeMiddleware Env inference Close #715pull/811/head
parent
c6bbcb8f9d
commit
2eec6f6fd9
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/zod-openapi': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
introduce routeMiddleware Env inference
|
|
@ -207,6 +207,68 @@ export type OpenAPIHonoOptions<E extends Env> = {
|
||||||
}
|
}
|
||||||
type HonoInit<E extends Env> = ConstructorParameters<typeof Hono>[0] & OpenAPIHonoOptions<E>
|
type HonoInit<E extends Env> = ConstructorParameters<typeof Hono>[0] & OpenAPIHonoOptions<E>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns `T | T[] | undefined` into `T[]`
|
||||||
|
*/
|
||||||
|
type AsArray<T> = T extends undefined // TODO move to utils?
|
||||||
|
? []
|
||||||
|
: T extends any[]
|
||||||
|
? T
|
||||||
|
: [T]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like simplify but recursive
|
||||||
|
*/
|
||||||
|
export type DeepSimplify<T> = {
|
||||||
|
// TODO move to utils?
|
||||||
|
[KeyType in keyof T]: T[KeyType] extends object ? DeepSimplify<T[KeyType]> : T[KeyType]
|
||||||
|
} & {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to infer generics from {@link MiddlewareHandler}
|
||||||
|
*/
|
||||||
|
export type OfHandlerType<T extends MiddlewareHandler> = T extends MiddlewareHandler<
|
||||||
|
infer E,
|
||||||
|
infer P,
|
||||||
|
infer I
|
||||||
|
>
|
||||||
|
? {
|
||||||
|
env: E
|
||||||
|
path: P
|
||||||
|
input: I
|
||||||
|
}
|
||||||
|
: never
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce a tuple of middleware handlers into a single
|
||||||
|
* handler representing the composition of all
|
||||||
|
* handlers.
|
||||||
|
*/
|
||||||
|
export type MiddlewareToHandlerType<M extends MiddlewareHandler<any, any, any>[]> = M extends [
|
||||||
|
infer First,
|
||||||
|
infer Second,
|
||||||
|
...infer Rest
|
||||||
|
]
|
||||||
|
? First extends MiddlewareHandler<any, any, any>
|
||||||
|
? Second extends MiddlewareHandler<any, any, any>
|
||||||
|
? Rest extends MiddlewareHandler<any, any, any>[] // Ensure Rest is an array of MiddlewareHandler
|
||||||
|
? MiddlewareToHandlerType<
|
||||||
|
[
|
||||||
|
MiddlewareHandler<
|
||||||
|
DeepSimplify<OfHandlerType<First>['env'] & OfHandlerType<Second>['env']>, // Combine envs
|
||||||
|
OfHandlerType<First>['path'], // Keep path from First
|
||||||
|
OfHandlerType<First>['input'] // Keep input from First
|
||||||
|
>,
|
||||||
|
...Rest
|
||||||
|
]
|
||||||
|
>
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: M extends [infer Last]
|
||||||
|
? Last // Return the last remaining handler in the array
|
||||||
|
: never
|
||||||
|
|
||||||
export type RouteHandler<
|
export type RouteHandler<
|
||||||
R extends RouteConfig,
|
R extends RouteConfig,
|
||||||
E extends Env = Env,
|
E extends Env = Env,
|
||||||
|
@ -317,7 +379,10 @@ export class OpenAPIHono<
|
||||||
>(
|
>(
|
||||||
{ middleware: routeMiddleware, ...route }: R,
|
{ middleware: routeMiddleware, ...route }: R,
|
||||||
handler: Handler<
|
handler: Handler<
|
||||||
E,
|
// use the env from the middleware if it's defined
|
||||||
|
R['middleware'] extends MiddlewareHandler[] | MiddlewareHandler
|
||||||
|
? OfHandlerType<MiddlewareToHandlerType<AsArray<R['middleware']>>>['env'] & E
|
||||||
|
: E,
|
||||||
P,
|
P,
|
||||||
I,
|
I,
|
||||||
// If response type is defined, only TypedResponse is allowed.
|
// If response type is defined, only TypedResponse is allowed.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Env, Hono, ToSchema } from 'hono'
|
import type { Env, Hono, ToSchema } from 'hono'
|
||||||
import { assertType, describe, expectTypeOf, it } from 'vitest'
|
import { assertType, describe, expectTypeOf, it } from 'vitest'
|
||||||
import { OpenAPIHono, createRoute, z } from '../src/index'
|
import { MiddlewareToHandlerType, OfHandlerType, OpenAPIHono, createRoute, z } from '../src/index'
|
||||||
|
import { createMiddleware } from 'hono/factory'
|
||||||
import type { ExtractSchema } from 'hono/types'
|
import type { ExtractSchema } from 'hono/types'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
|
||||||
|
@ -234,3 +235,95 @@ describe('coerce', () => {
|
||||||
type verify = Expect<Equal<Expected, Actual>>
|
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({})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue