fix(zod-openapi): use `z.output` for types after validation (#164)
* fix(zod-openapi): use `z.output` for types after validation * changesetpull/165/head
parent
55373edb67
commit
62a97fda6a
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/zod-openapi': patch
|
||||
---
|
||||
|
||||
fix(zod-openapi): use `z.output` for types after validation
|
|
@ -31,7 +31,7 @@
|
|||
"zod": "3.*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"hono": "^3.5.8",
|
||||
"hono": "^3.6.3",
|
||||
"zod": "^3.22.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -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'
|
||||
|
@ -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>> }
|
||||
}
|
||||
: {}
|
||||
: {}
|
||||
|
@ -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']
|
||||
>
|
||||
}
|
||||
|
@ -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']
|
||||
>
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
@ -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',
|
||||
|
@ -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 })
|
||||
})
|
||||
|
@ -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 },
|
||||
},
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue