fix(zod-openapi): support default response (#855)

* fix(zod-openapi): support default response

* chore: changeset
pull/858/head
Jani 2024-11-29 13:07:33 +01:00 committed by GitHub
parent b1fdf7202f
commit 3f63c46fa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 10 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/zod-openapi': patch
---
fix: support default response

View File

@ -194,16 +194,26 @@ type RouteConfigStatusCode = keyof StatusCodeRangeDefinitions | StatusCode
type ExtractStatusCode<T extends RouteConfigStatusCode> = T extends keyof StatusCodeRangeDefinitions
? StatusCodeRangeDefinitions[T]
: T
export type RouteConfigToTypedResponse<R extends RouteConfig> = {
[Status in keyof R['responses'] &
RouteConfigStatusCode]: undefined extends R['responses'][Status]['content']
? TypedResponse<{}, ExtractStatusCode<Status>, string>
: ReturnJsonOrTextOrResponse<
keyof R['responses'][Status]['content'],
ExtractContent<R['responses'][Status]['content']>,
Status
>
}[keyof R['responses'] & RouteConfigStatusCode]
type DefinedStatusCodes<R extends RouteConfig> = keyof R['responses'] & RouteConfigStatusCode
export type RouteConfigToTypedResponse<R extends RouteConfig> =
| {
[Status in DefinedStatusCodes<R>]: undefined extends R['responses'][Status]['content']
? TypedResponse<{}, ExtractStatusCode<Status>, string>
: ReturnJsonOrTextOrResponse<
keyof R['responses'][Status]['content'],
ExtractContent<R['responses'][Status]['content']>,
Status
>
}[DefinedStatusCodes<R>]
| ('default' extends keyof R['responses']
? undefined extends R['responses']['default']['content']
? TypedResponse<{}, Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>, string>
: ReturnJsonOrTextOrResponse<
keyof R['responses']['default']['content'],
ExtractContent<R['responses']['default']['content']>,
Exclude<StatusCode, ExtractStatusCode<DefinedStatusCodes<R>>>
>
: never)
export type Hook<T, E extends Env, P extends string, R> = (
result: { target: keyof ValidationTargets } & (

View File

@ -147,4 +147,86 @@ describe('supports async handler', () => {
const hono = new OpenAPIHono()
hono.openapi(route, handler)
})
test('handler should support default response for unspecified status codes', () => {
const routeWithDefault = createRoute({
method: 'get',
path: '/users',
responses: {
200: {
content: {
'application/json': {
schema: z.object({ id: z.string() }),
},
},
description: 'Success response',
},
default: {
content: {
'application/json': {
schema: z.object({ error: z.string() }),
},
},
description: 'Error response',
},
},
})
const errorHandler: RouteHandler<typeof routeWithDefault> = (c) => {
return c.json({ error: 'Server Error' }, 500)
}
const hono = new OpenAPIHono()
hono.openapi(routeWithDefault, errorHandler)
})
test('handler should respect explicitly defined status codes with default fallback', () => {
const routeWithDefault = createRoute({
method: 'get',
path: '/users',
responses: {
200: {
content: {
'application/json': {
schema: z.object({ id: z.string() }),
},
},
description: 'Success response',
},
404: {
content: {
'application/json': {
schema: z.object({ message: z.string() }),
},
},
description: 'Not found response',
},
default: {
content: {
'application/json': {
schema: z.object({ error: z.string() }),
},
},
description: 'Error response',
},
},
})
const successHandler: RouteHandler<typeof routeWithDefault> = (c) => {
return c.json({ id: '123' }, 200)
}
const notFoundHandler: RouteHandler<typeof routeWithDefault> = (c) => {
return c.json({ message: 'Not Found' }, 404)
}
const errorHandler: RouteHandler<typeof routeWithDefault> = (c) => {
return c.json({ error: 'Server Error' }, 500)
}
const hono = new OpenAPIHono()
hono.openapi(routeWithDefault, successHandler)
hono.openapi(routeWithDefault, notFoundHandler)
hono.openapi(routeWithDefault, errorHandler)
})
})