Hi team,
I encountered this issue when trying to build an API with more than one input type (e.g. PATH + QUERY like /{path}?q=xxx
).
When using zod-openapi
with a route including query
input data with (or without) params
, the type resolution fails when trying to access the input from the handler.
In this context (the sample file is provided at the end of this post)
app.openapi(route, async (c) => {
const { paramValue } = c.req.valid('param') as ParamSchema;
const { queryValue } = c.req.valid('query') as QuerySchema;
...
});
the calls to c.req.valid
will produce the following errors at compile time:
src/min.ts:43:40 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
43 const { paramValue } = c.req.valid('param') as ParamSchema;
~~~~~~~
src/min.ts:44:40 - error [tsc.trace.both.gz](https://github.com/honojs/middleware/files/12908484/tsc.trace.both.gz): Argument of type 'string' is not assignable to parameter of type 'never'.
44 const { queryValue } = c.req.valid('query') as QuerySchema;
~~~~~~~
Found 2 errors in the same file, starting at: src/min.ts:43
that is the output of bun run tsc --noEmit --pretty --skipLibCheck --strict src/min.ts
Note that the problem disappears if we just use the param
as input:
const route = createRoute({
method: 'get',
path: '/{paramValue}/path',
request: {
params: ParamSchema,
// query: QuerySchema,
},
...
});
app.openapi(route, async (c) => {
const { paramValue } = c.req.valid('param') as ParamSchema; // Works !
...
});
But not when commenting out the param
and leaving just the query:
const route = createRoute({
method: 'get',
path: '/path', // Note the {param} is removed from the path
request: {
//params: ParamSchema,
query: QuerySchema,
},
...
});
const app = new OpenAPIHono();
app.openapi(route, async (c) => {
// error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
const { queryValue } = c.req.valid('query') as QuerySchema;
...
});
Build traces
Looking at the build traces from tsc
, we can see for param
the display type is correct <T extends \"param\">(target: T) ...
allowing to proceed without any errors.
Param only
{"id":15461,"symbolName":"valid","recursionId":3796,"firstDeclaration":{"path":"/home/user/Documents/substreams-clock-api/node_modules/hono/dist/types/request.d.ts","start":{"line":57,"character":71},"end":{"line":58,"character":94}},"flags":["Object"],"display":"<T extends \"param\">(target: T) => InputToDataByTarget<{ param: { paramValue: \"a\" | \"b\" | \"c\"; }; }, T>"},
However in the other cases, it resolves to <T extends never>(target: T) ...
making it impossible to infer the type.
Query only
{"id":15453,"symbolName":"valid","recursionId":3793,"firstDeclaration":{"path":"/home/user/Documents/substreams-clock-api/node_modules/hono/dist/types/request.d.ts","start":{"line":57,"character":71},"end":{"line":58,"character":94}},"flags":["Object"],"display":"<T extends never>(target: T) => InputToDataByTarget<undefined, T> | InputToDataByTarget<Partial<{ json: unknown; form: unknown; query: unknown; queries: unknown; param: unknown; header: unknown; cookie: unknown; }>, T>"},
Both
{"id":15472,"symbolName":"valid","recursionId":3803,"firstDeclaration":{"path":"/home/user/Documents/substreams-clock-api/node_modules/hono/dist/types/request.d.ts","start":{"line":57,"character":71},"end":{"line":58,"character":94}},"flags":["Object"],"display":"<T extends never>(target: T) => InputToDataByTarget<undefined, T> | InputToDataByTarget<Partial<{ json: unknown; form: unknown; query: unknown; queries: unknown; param: unknown; header: unknown; cookie: unknown; }>, T>"},
The full traces for each are available here: tsc.traces.tar.gz
Related
In #77, the problem appeared to be fixed by using strict: true
for the config and upgrading to the latest versions of hono
and @hono/zod-validator
. This doesn't seem to fix the problem in this case.
System information
Bun version 1.0.2
bun pm ls
Sample file
import { OpenAPIHono, z, createRoute } from '@hono/zod-openapi';
import { TypedResponse } from 'hono';
const ParamSchema = z.object({
paramValue: z.enum(['a', 'b', 'c'])
.openapi({
param: {
name: 'paramValue',
in: 'path',
}
})
});
type ParamSchema = z.infer<typeof ParamSchema>;
const QuerySchema = z.object({
queryValue: z.coerce.number()
.openapi({
param: {
name: 'queryValue',
in: 'query',
}
})
});
type QuerySchema = z.infer<typeof QuerySchema>;
const route = createRoute({
method: 'get',
path: '/{paramValue}/path',
request: {
params: ParamSchema,
query: QuerySchema,
},
responses: {
200: {
description: 'Sample endpoint',
},
},
});
const app = new OpenAPIHono();
app.openapi(route, async (c) => {
const { paramValue } = c.req.valid('param') as ParamSchema;
const { queryValue } = c.req.valid('query') as QuerySchema;
return {
response: c.text("Not working...")
} as TypedResponse<string>;
});
export default app;