Skip to content

Commit

Permalink
fix(zod-openapi): use z.output for types after validation (#164)
Browse files Browse the repository at this point in the history
* fix(zod-openapi): use `z.output` for types after validation

* changeset
  • Loading branch information
yusukebe authored Sep 19, 2023
1 parent 55373ed commit 62a97fd
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 39 deletions.
5 changes: 5 additions & 0 deletions .changeset/nasty-bikes-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/zod-openapi': patch
---

fix(zod-openapi): use `z.output` for types after validation
2 changes: 1 addition & 1 deletion packages/zod-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"zod": "3.*"
},
"devDependencies": {
"hono": "^3.5.8",
"hono": "^3.6.3",
"zod": "^3.22.1"
},
"dependencies": {
Expand Down
39 changes: 19 additions & 20 deletions packages/zod-openapi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import type {
ZodContentObject,
ZodRequestBody,
} from '@asteasolutions/zod-to-openapi'
import { OpenApiGeneratorV3, OpenApiGeneratorV31, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'
import {
OpenApiGeneratorV3,
OpenApiGeneratorV31,
OpenAPIRegistry,
} from '@asteasolutions/zod-to-openapi'
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'
import type { OpenAPIObjectConfig } from '@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator'
import { zValidator } from '@hono/zod-validator'
Expand Down Expand Up @@ -60,7 +64,7 @@ type InputTypeBase<
? RequestPart<R, Part> extends AnyZodObject
? {
in: { [K in Type]: z.input<RequestPart<R, Part>> }
out: { [K in Type]: z.input<RequestPart<R, Part>> }
out: { [K in Type]: z.output<RequestPart<R, Part>> }
}
: {}
: {}
Expand All @@ -78,7 +82,7 @@ type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes
>
}
out: {
json: z.input<
json: z.output<
R['request']['body']['content'][keyof R['request']['body']['content']]['schema']
>
}
Expand All @@ -101,7 +105,7 @@ type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes
>
}
out: {
form: z.input<
form: z.output<
R['request']['body']['content'][keyof R['request']['body']['content']]['schema']
>
}
Expand Down Expand Up @@ -147,7 +151,7 @@ type ConvertPathType<T extends string> = T extends `${infer _}/{${infer Param}}$

type HandlerResponse<O> = TypedResponse<O> | Promise<TypedResponse<O>>

type HonoInit = ConstructorParameters<typeof Hono>[0];
type HonoInit = ConstructorParameters<typeof Hono>[0]

export class OpenAPIHono<
E extends Env = Env,
Expand Down Expand Up @@ -281,33 +285,26 @@ export class OpenAPIHono<
app.openAPIRegistry.definitions.forEach((def) => {
switch (def.type) {
case 'component':
return this.openAPIRegistry.registerComponent(
def.componentType,
def.name,
def.component
)

return this.openAPIRegistry.registerComponent(def.componentType, def.name, def.component)

case 'route':
return this.openAPIRegistry.registerPath({
...def.route,
path: `${path}${def.route.path}`
path: `${path}${def.route.path}`,
})

case 'webhook':
return this.openAPIRegistry.registerWebhook({
...def.webhook,
path: `${path}${def.webhook.path}`
path: `${path}${def.webhook.path}`,
})

case 'schema':
return this.openAPIRegistry.register(
def.schema._def.openapi._internal.refId,
def.schema
)
return this.openAPIRegistry.register(def.schema._def.openapi._internal.refId, def.schema)

case 'parameter':
return this.openAPIRegistry.registerParameter(
def.schema._def.openapi._internal.refId,
def.schema._def.openapi._internal.refId,
def.schema
)

Expand All @@ -323,7 +320,9 @@ export class OpenAPIHono<
}
}

type RoutingPath<P extends string> = P extends `${infer Head}/{${infer Param}}${infer Tail}` ? `${Head}/:${Param}${RoutingPath<Tail>}` : P
type RoutingPath<P extends string> = P extends `${infer Head}/{${infer Param}}${infer Tail}`
? `${Head}/:${Param}${RoutingPath<Tail>}`
: P

export const createRoute = <P extends string, R extends Omit<RouteConfig, 'path'> & { path: P }>(
routeConfig: R
Expand All @@ -332,7 +331,7 @@ export const createRoute = <P extends string, R extends Omit<RouteConfig, 'path'
...routeConfig,
getRoutingPath(): RoutingPath<R['path']> {
return routeConfig.path.replaceAll(/\/{(.+?)}/g, '/:$1') as RoutingPath<P>
}
},
}
}

Expand Down
49 changes: 31 additions & 18 deletions packages/zod-openapi/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ describe('Constructor', () => {

it('Should accept init object', () => {
const getPath = () => ''
const app = new OpenAPIHono({getPath})

const app = new OpenAPIHono({ getPath })
expect(app.getPath).toBe(getPath)
})
})
Expand All @@ -21,20 +20,31 @@ describe('Basic - params', () => {
const ParamsSchema = z.object({
id: z
.string()
.min(3)
.transform((val, ctx) => {
const parsed = parseInt(val)
if (isNaN(parsed)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Not a number',
})
return z.NEVER
}
return parsed
})
.openapi({
param: {
name: 'id',
in: 'path',
},
example: '1212121',
example: 123,
type: 'integer',
}),
})

const UserSchema = z
.object({
id: z.string().openapi({
example: '123',
id: z.number().openapi({
example: 123,
}),
name: z.string().openapi({
example: 'John Doe',
Expand Down Expand Up @@ -116,14 +126,14 @@ describe('Basic - params', () => {
const res = await app.request('/users/123')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
id: '123',
id: 123,
age: 20,
name: 'Ultra-man',
})
})

it('Should return 400 response with correct contents', async () => {
const res = await app.request('/users/1')
const res = await app.request('/users/abc')
expect(res.status).toBe(400)
expect(await res.json()).toEqual({ ok: false })
})
Expand All @@ -139,7 +149,7 @@ describe('Basic - params', () => {
User: {
type: 'object',
properties: {
id: { type: 'string', example: '123' },
id: { type: 'number', example: 123 },
name: { type: 'string', example: 'John Doe' },
age: { type: 'number', example: 42 },
},
Expand All @@ -158,7 +168,7 @@ describe('Basic - params', () => {
get: {
parameters: [
{
schema: { type: 'string', minLength: 3, example: '1212121' },
schema: { type: 'integer', example: 123 },
required: true,
name: 'id',
in: 'path',
Expand Down Expand Up @@ -626,23 +636,26 @@ describe('Routers', () => {
})
it('Should include definitions from nested routers', () => {
const router = new OpenAPIHono().openapi(route, (ctx) => {
return ctx.jsonT({id: 123})
return ctx.jsonT({ id: 123 })
})

router.openAPIRegistry.register('Id', z.number())

router.openAPIRegistry.registerParameter('Key', z.number().openapi({
param: {in: 'path'}
}))
router.openAPIRegistry.registerParameter(
'Key',
z.number().openapi({
param: { in: 'path' },
})
)

router.openAPIRegistry.registerWebhook({
method: 'post',
path: '/postback',
responses: {
200: {
description: 'Receives a post back'
}
}
description: 'Receives a post back',
},
},
})

const app = new OpenAPIHono().route('/api', router)
Expand All @@ -653,7 +666,7 @@ describe('Routers', () => {
version: '1.0.0',
},
})

expect(json.components?.schemas).toHaveProperty('Id')
expect(json.components?.schemas).toHaveProperty('Post')
expect(json.components?.parameters).toHaveProperty('Key')
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5864,6 +5864,11 @@ hono@^3.5.8:
resolved "https://registry.yarnpkg.com/hono/-/hono-3.5.8.tgz#9bbc412f5a54183cf2a81a36a9b9ea56da10f785"
integrity sha512-ZipTmGfHm43q5QOEBGog2wyejyNUcicjPt0BLDQ8yz9xij/y9RYXRpR1YPxMpQqeyNM7isvpsIAe9Ems51Wq0Q==

hono@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/hono/-/hono-3.6.3.tgz#0dab94a9e49dadc0f99bf8b8ffc70b223f53ab9f"
integrity sha512-8WszeHGzUm45qJy2JcCXkEFXMsAysciGGQs+fbpdUYPO2bRMbjJznZE3LX8tCXBqR4f/3e6225B3YOX6pQZWvA==

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 62a97fd

Please sign in to comment.