From 70a564e268cd6350cfb994e5b5c5626b31a3fcc9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Feb 2025 10:21:56 +0100 Subject: [PATCH] fix(zod-openapi): handle nested app base paths in openapi registration (#955) * feat: handle nested app base paths in openapi registration * test: add test for nested base paths in OpenAPI schema * test(zod-openapi): add route without base path * chore: add changeset * chore: format --- .changeset/heavy-trains-serve.md | 5 ++ packages/zod-openapi/src/index.ts | 14 ++++- packages/zod-openapi/test/index.test.ts | 83 +++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 .changeset/heavy-trains-serve.md diff --git a/.changeset/heavy-trains-serve.md b/.changeset/heavy-trains-serve.md new file mode 100644 index 00000000..62080043 --- /dev/null +++ b/.changeset/heavy-trains-serve.md @@ -0,0 +1,5 @@ +--- +'@hono/zod-openapi': patch +--- + +fix: use nested app base paths in openapi schema diff --git a/packages/zod-openapi/src/index.ts b/packages/zod-openapi/src/index.ts index af492201..f150e407 100644 --- a/packages/zod-openapi/src/index.ts +++ b/packages/zod-openapi/src/index.ts @@ -632,13 +632,23 @@ export class OpenAPIHono< case 'route': return this.openAPIRegistry.registerPath({ ...def.route, - path: mergePath(pathForOpenAPI, def.route.path), + path: mergePath( + pathForOpenAPI, + // @ts-expect-error _basePath is private + app._basePath, + def.route.path + ), }) case 'webhook': return this.openAPIRegistry.registerWebhook({ ...def.webhook, - path: mergePath(pathForOpenAPI, def.webhook.path), + path: mergePath( + pathForOpenAPI, + // @ts-expect-error _basePath is private + app._basePath, + def.webhook.path + ), }) case 'schema': diff --git a/packages/zod-openapi/test/index.test.ts b/packages/zod-openapi/test/index.test.ts index 8f08f3c8..8bf08b5f 100644 --- a/packages/zod-openapi/test/index.test.ts +++ b/packages/zod-openapi/test/index.test.ts @@ -1156,6 +1156,89 @@ describe('basePath()', () => { const data = (await res.json()) as any expect(Object.keys(data.paths)[0]).toBe('/api/message') }) + + it('Should add nested base paths to openapi schema', async () => { + const app = new OpenAPIHono() + + const v1 = new OpenAPIHono().basePath('/api/v1') + v1.openapi( + { + method: 'get', + path: '/message1', + responses: { + 200: { + description: 'Get message', + content: { + 'application/json': { + schema: z.object({ message: z.string() }), + }, + }, + }, + }, + }, + (c) => c.json({ message: 'Hello' }) + ) + + const v2 = new OpenAPIHono().basePath('/api/v2') + v2.openapi( + { + method: 'get', + path: '/message2', + responses: { + 200: { + description: 'Get message', + content: { + 'application/json': { + schema: z.object({ message: z.string() }), + }, + }, + }, + }, + }, + (c) => c.json({ message: 'Hello' }) + ) + + app.route('/', v1) + app.route('/', v2) + + app.openapi( + { + method: 'get', + path: '/hello', + responses: { + 200: { + description: 'Get message', + content: { + 'application/json': { + schema: z.object({ message: z.string() }), + }, + }, + }, + }, + }, + (c) => c.json({ message: 'Hello' }) + ) + + const res1 = await app.request('/api/v1/message1') + expect(res1.status).toBe(200) + + const res2 = await app.request('/api/v2/message2') + expect(res2.status).toBe(200) + + const json = app.getOpenAPIDocument({ + openapi: '3.0.0', + info: { + version: '1.0.0', + title: 'My API', + }, + }) + + const paths = Object.keys(json.paths) + + expect(paths).toStrictEqual(['/api/v1/message1', '/api/v2/message2', '/hello']) + + expect(paths).not.toStrictEqual(['/message1', '/message2', '/hello']) + }) }) describe('With hc', () => {