Skip to content

Commit

Permalink
fix: adjust paths with params to the OpenAPI format
Browse files Browse the repository at this point in the history
OpenAPI requires paths with params to be in this format: /path/{param}, while Hono uses /path/:param. Prior to this fix, path params in Swagger were not working properly, i.e. they weren't properly replaced.
  • Loading branch information
paolostyle committed Nov 11, 2024
1 parent aa451af commit 0f52006
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ coverage/
test-results/
.DS_Store
.npmrc

# temporarily
examples
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@vitest/ui": "^2.1.4",
"husky": "^9.1.6",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.6.3",
"vitest": "^2.1.4"
},
Expand Down
25 changes: 12 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 55 additions & 1 deletion src/createOpenApiDocument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Hono } from 'hono';
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';
import { createOpenApiDocument } from './createOpenApiDocument.ts';
import {
createOpenApiDocument,
normalizePathParams,
} from './createOpenApiDocument.ts';
import { defineOpenApiOperation, openApi } from './openApi.ts';
import type {
HonoOpenApiOperation,
Expand Down Expand Up @@ -319,4 +322,55 @@ describe('createOpenApiDocument', () => {

createOpenApiDocument(app, documentData);
});

it('normalizes path parameters correctly', async () => {
const app = new Hono().get(
'/user/:id',
openApi({
request: {
param: z.object({ id: z.string() }),
},
responses: {
200: z.object({ id: z.string() }),
},
}),
async (c) => {
const { id } = c.req.valid('param');
return c.json({ id }, 200);
},
);

createOpenApiDocument(app, documentData);

const response = await app.request('/doc');
const openApiSpec = await response.json();

expect(openApiSpec.paths['/user/{id}'].get.parameters).toEqual([
{
in: 'path',
name: 'id',
required: true,
schema: {
type: 'string',
},
},
]);

expect(openApiSpec.paths['/user/{id}']).toBeDefined();
});
});

describe('normalizePathParams', () => {
it.each`
honoPath | openApiPath
${':date{[0-9]+}'} | ${'{date}'}
${'/post/:date{[0-9]+}/:title{[a-z]+}'} | ${'/post/{date}/{title}'}
${'/posts/:filename{.+\\.png$}'} | ${'/posts/{filename}'}
${'/api/animal/:type?'} | ${'/api/animal/{type}'}
${'/:kek-1'} | ${'/{kek-1}'}
${'/:kek_id'} | ${'/{kek_id}'}
${'/posts/:id/comment/:comment_id'} | ${'/posts/{id}/comment/{comment_id}'}
`('normalizes $honoPath to $openApiPath', ({ honoPath, openApiPath }) => {
expect(normalizePathParams(honoPath)).toBe(openApiPath);
});
});
16 changes: 11 additions & 5 deletions src/createOpenApiDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,21 @@ export function createOpenApiDocument<
const { request, responses, ...rest } = (route.handler as any)[
OpenApiSymbol
] as HonoOpenApiOperation;
const path = `${route.method} ${route.path}`;

const path = normalizePathParams(route.path);
const pathWithMethod = `${route.method} ${path}`;

const operation: ZodOpenApiOperationObject = {
responses: processResponses(responses, path),
responses: processResponses(responses, pathWithMethod),
...(request ? processRequest(request) : {}),
...rest,
};

if (!(route.path in paths)) {
paths[route.path] = {};
if (!(path in paths)) {
paths[path] = {};
}

paths[route.path][route.method.toLowerCase() as Method] = operation;
paths[path][route.method.toLowerCase() as Method] = operation;
}

const openApiDoc = createDocument({
Expand Down Expand Up @@ -163,3 +165,7 @@ export const processResponses = (
}),
);
};

export const normalizePathParams = (path: string): string => {
return path.replace(/:([a-zA-Z0-9-_]+)\??(\{.*?\})?/g, '{$1}');
};

0 comments on commit 0f52006

Please sign in to comment.