diff --git a/.changeset/lovely-laws-cry.md b/.changeset/lovely-laws-cry.md new file mode 100644 index 00000000..4e706d24 --- /dev/null +++ b/.changeset/lovely-laws-cry.md @@ -0,0 +1,5 @@ +--- +'@hono/zod-openapi': minor +--- + +Add getRoutingPath to the return value of createRoute. diff --git a/packages/zod-openapi/README.md b/packages/zod-openapi/README.md index 4df2be10..1968240e 100644 --- a/packages/zod-openapi/README.md +++ b/packages/zod-openapi/README.md @@ -203,6 +203,18 @@ import { prettyJSON } from 'hono/pretty-json' app.use('/doc/*', prettyJSON()) ``` +### Configure middleware for each endpoint + +You can configure middleware for each endpoint from a route created by `createRoute` as follows. + +```ts +import { prettyJSON } from 'hono/pretty-json' +import { cache } from 'honoc/cache' + +app.use(route.getRoutingPath(), prettyJSON(), cache({ cacheName: "my-cache" })) +app.openapi(route, handler) +``` + ### RPC Mode Zod OpenAPI Hono supports Hono's RPC mode. You can define types for the Hono Client as follows: diff --git a/packages/zod-openapi/src/index.ts b/packages/zod-openapi/src/index.ts index 7b49c512..69cc38f2 100644 --- a/packages/zod-openapi/src/index.ts +++ b/packages/zod-openapi/src/index.ts @@ -323,9 +323,18 @@ export class OpenAPIHono< } } +type RoutingPath

= P extends `${infer Head}/{${infer Param}}${infer Tail}` ? `${Head}/:${Param}${RoutingPath}` : P + export const createRoute =

& { path: P }>( routeConfig: R -) => routeConfig +) => { + return { + ...routeConfig, + getRoutingPath(): RoutingPath { + return routeConfig.path.replaceAll(/\/{(.+?)}/g, '/:$1') as RoutingPath

+ } + } +} extendZodWithOpenApi(z) export { z } diff --git a/packages/zod-openapi/test/createRoute.test.ts b/packages/zod-openapi/test/createRoute.test.ts new file mode 100644 index 00000000..14fe2790 --- /dev/null +++ b/packages/zod-openapi/test/createRoute.test.ts @@ -0,0 +1,62 @@ +/* eslint-disable node/no-extraneous-import */ +import { describe, it, expect, expectTypeOf } from 'vitest' +import { createRoute, z } from '../src' + +describe('createRoute', () => { + it.each([ + { path: '/users', expected: '/users' }, + { path: '/users/{id}', expected: '/users/:id' }, + { path: '/users/{uid}/posts/{postId}', expected: '/users/:uid/posts/:postId' }, + ])('createRoute(%j)', ({ path, expected }) => { + const ParamsSchema = z.object({ + id: z + .string() + .min(3) + .openapi({ + param: { + name: 'id', + in: 'path', + }, + example: '1212121', + }), + }) + + const UserSchema = z.object({ + id: z.string().openapi({ + example: '123', + }), + name: z.string().openapi({ + example: 'John Doe', + }), + age: z.number().openapi({ + example: 42, + }), + }) + + const config = { + method: 'get', + path, + request: { + params: ParamsSchema, + }, + responses: { + 200: { + content: { + 'application/json': { + schema: UserSchema, + }, + }, + description: 'Retrieve the user', + }, + }, + } as const + const route = createRoute(config) + + expect(route).toEqual({ + ...config, + getRoutingPath: expect.any(Function), + }) + expect(route.getRoutingPath()).toBe(expected) + expectTypeOf(route.getRoutingPath()).toEqualTypeOf() + }) +})