Skip to content

Commit

Permalink
fix(zod-openapi): Strict type checking of return values (#287)
Browse files Browse the repository at this point in the history
* changeset

* fix(zod-openapi): Strict type checking of return values

* bump hono `v3.11.1`

* remove changeset

* add changeset
  • Loading branch information
yusukebe authored Dec 4, 2023
1 parent d8300e5 commit 1568b92
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-donkeys-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/zod-openapi': patch
---

fix: Strict type checking of return values
6 changes: 3 additions & 3 deletions packages/zod-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
},
"homepage": "https://github.com/honojs/middleware",
"peerDependencies": {
"hono": ">=3.9.0",
"hono": ">=3.11.1",
"zod": "3.*"
},
"devDependencies": {
"@hono/zod-validator": "^0.1.11",
"hono": "^3.9.1",
"hono": "^3.11.1",
"zod": "^3.22.1"
},
"dependencies": {
Expand All @@ -53,4 +53,4 @@
"engines": {
"node": ">=16.0.0"
}
}
}
48 changes: 41 additions & 7 deletions packages/zod-openapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ResponseConfig,
RouteConfig,
ZodContentObject,
ZodMediaTypeObject,
ZodRequestBody,
} from '@asteasolutions/zod-to-openapi'
import {
Expand Down Expand Up @@ -150,11 +151,12 @@ type ConvertPathType<T extends string> = T extends `${infer Start}/{${infer Para
? `${Start}/:${Param}${ConvertPathType<Rest>}`
: T

type HandlerResponse<O> =
| TypedResponse<O>
| Promise<TypedResponse<O>>
type HandlerTypedResponse<O> = TypedResponse<O> | Promise<TypedResponse<O>>
type HandlerAllResponse<O> =
| Response
| Promise<Response>
| TypedResponse<O>
| Promise<TypedResponse<O>>

export type OpenAPIHonoOptions<E extends Env> = {
defaultHook?: Hook<any, E, any, any>
Expand All @@ -171,7 +173,23 @@ export type RouteHandler<
InputTypeForm<R> &
InputTypeJson<R>,
P extends string = ConvertPathType<R['path']>
> = Handler<E, P, I, HandlerResponse<OutputType<R>>>
> = Handler<
E,
P,
I,
// If response type is defined, only TypedResponse is allowed.
R extends {
responses: {
[statusCode: string]: {
content: {
[mediaType: string]: ZodMediaTypeObject
}
}
}
}
? HandlerTypedResponse<OutputType<R>>
: HandlerAllResponse<OutputType<R>>
>

export type RouteHook<
R extends RouteConfig,
Expand All @@ -186,8 +204,8 @@ export type RouteHook<
> = Hook<I, E, P, OutputType<R>>

export type OpenAPIObjectConfigure<E extends Env, P extends string> =
OpenAPIObjectConfig |
((context: Context<E, P>) => OpenAPIObjectConfig)
| OpenAPIObjectConfig
| ((context: Context<E, P>) => OpenAPIObjectConfig)

export class OpenAPIHono<
E extends Env = Env,
Expand All @@ -214,7 +232,23 @@ export class OpenAPIHono<
P extends string = ConvertPathType<R['path']>
>(
route: R,
handler: Handler<E, P, I, HandlerResponse<OutputType<R>>>,
handler: Handler<
E,
P,
I,
// If response type is defined, only TypedResponse is allowed.
R extends {
responses: {
[statusCode: string]: {
content: {
[mediaType: string]: ZodMediaTypeObject
}
}
}
}
? HandlerTypedResponse<OutputType<R>>
: HandlerAllResponse<OutputType<R>>
>,
hook: Hook<I, E, P, OutputType<R>> | undefined = this.defaultHook
): OpenAPIHono<E, S & ToSchema<R['method'], P, I['in'], OutputType<R>>, BasePath> => {
this.openAPIRegistry.registerPath(route)
Expand Down
12 changes: 10 additions & 2 deletions packages/zod-openapi/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,14 @@ describe('Types', () => {
>
expectTypeOf(appRoutes).toMatchTypeOf<H>
})

// @ts-expect-error it should throw an error if the types are wrong
app.openapi(route, (c) => {
return c.jsonT({
id: '123', // should be number
message: 'Success',
})
})
})

describe('Routers', () => {
Expand Down Expand Up @@ -1140,7 +1148,7 @@ describe('Context can be accessible in the doc route', () => {
}
)

app.doc('/doc', context => ({
app.doc('/doc', (context) => ({
openapi: '3.0.0',
info: {
version: '1.0.0',
Expand All @@ -1149,7 +1157,7 @@ describe('Context can be accessible in the doc route', () => {
}))

it('Should return with the title set as specified in env', async () => {
const res = await app.request('/doc', null, { TITLE: 'My API' })
const res = await app.request('/doc', {}, { TITLE: 'My API' })
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
openapi: '3.0.0',
Expand Down
7 changes: 6 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6935,11 +6935,16 @@ heap-js@^2.2.0:
resolved "https://registry.yarnpkg.com/heap-js/-/heap-js-2.3.0.tgz#8eed2cede31ec312aa696eef1d4df0565841f183"
integrity sha512-E5303mzwQ+4j/n2J0rDvEPBN7GKjhis10oHiYOgjxsmxYgqG++hz9NyLLOXttzH8as/DyiBHYpUrJTZWYaMo8Q==

hono@^3.0.0, hono@^3.1.0, hono@^3.1.2, hono@^3.1.5, hono@^3.5.1, hono@^3.5.2, hono@^3.5.8, hono@^3.7.2, hono@^3.7.3, hono@^3.9.1, hono@^3.9.2:
hono@^3.0.0, hono@^3.1.0, hono@^3.1.2, hono@^3.1.5, hono@^3.5.1, hono@^3.5.2, hono@^3.5.8, hono@^3.7.2, hono@^3.7.3, hono@^3.9.2:
version "3.9.2"
resolved "https://registry.yarnpkg.com/hono/-/hono-3.9.2.tgz#db31a6ce733131ee16bce0c9bd031a0708ebe052"
integrity sha512-180NOiMadqU3lGmN6ajPDZvZPWus3a9mtVaAUR9uG0SImngBwRLA8vbnV0oUfUAgFT4nX55sGV9dVA06OuikHA==

hono@^3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/hono/-/hono-3.11.1.tgz#2f5893ae02baf55e1c5ad1e5b3a75936e3942c88"
integrity sha512-8FNUh8p/dkw8qYFxy8IJA500iW9NSeuvuwRfLw0FGkE/blCxtqWqxWTB+NaLSK3qGpaudzKur6s40Q0kpO0E+w==

hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
Expand Down

0 comments on commit 1568b92

Please sign in to comment.