openapi-typescript
openapi-typescript copied to clipboard
params always required
The following snippet of code should be valid, as per the README:
import createClient from "openapi-fetch";
import type { paths } from "./petstore"; // npx openapi-typescript https://petstore3.swagger.io/api/v3/openapi.yaml -o petstore.d.ts
const client = createClient<paths>();
client.POST("/store/order", {
body: {
id: 0,
},
})
However, it is failing with:
error TS2345: Argument of type '{ body: {}; }' is not assignable to parameter of type '{ params: { query?: never; header?: never; path?: never; cookie?: never; }; } & { body?: { id?: number; petId?: number; quantity?: number; shipDate?: string; status?: "placed" | "approved" | "delivered"; complete?: boolean; }; } & { ...; } & Omit<...> & { ...; }'.
Property 'params' is missing in type '{ body: {}; }' but required in type '{ params: { query?: never; header?: never; path?: never; cookie?: never; }; }'.
6 client.POST("/store/order", {
~
7 body: {
~~~~~~~~~~~
...
9 },
~~~~~~
10 })
~
node_modules/openapi-fetch/dist/index.d.ts:87:9
87 : { params: T["parameters"] }
~~~~~~
'params' is declared here.
If I change it to:
import createClient from "openapi-fetch";
import type { paths } from "./petstore"; // npx openapi-typescript https://petstore3.swagger.io/api/v3/openapi.yaml -o petstore.d.ts
const client = createClient<paths>();
client.POST("/store/order", {
params: {},
body: {
id: 0,
},
})
the error goes away.
Is this a recent regression? I don't remember it doing this before. I'm using typescript 5.5.4, openapi-fetch 0.10.2, and openapi-typscript 7.1.0.
my tsconfig.json:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"noImplicitAny": true,
},
"files": [
"test.ts"
]
}
Checklist
- [ ] I’m willing to open a PR (see CONTRIBUTING.md)
I think the problem is here:
export type FindRequiredKeys<T, K extends keyof T> = K extends unknown ? (undefined extends T[K] ? never : K) : K;
/** Does this object contain required keys? */
export type HasRequiredKeys<T> = FindRequiredKeys<T, keyof T>;
A couple problems:
K extends unknownseems to always be true (how canK extends unknownever be false?)undefined extends T[K]seems to always be false (how canundefined extends T[K]ever be true?)
If I change it to this:
export type FindRequiredKeys<T, K extends keyof T> = never extends T[K] ? never : K;
/** Does this object contain required keys? */
export type HasRequiredKeys<T> = FindRequiredKeys<T, keyof T>;
It fixes it, but I'm not sure if that causes any regressions.
I tracked this down to behavioral differences based on the strictNullChecks (or strict, which includes strictNullChecks) compiler option. When that option is false (the default), undefined is considered to be a subtype of every other type except never. In that case, given this definition:
type Parameters = {
query?: {
name?: string;
status?: string;
};
header?: never;
path: {
petId: number;
};
cookie?: never;
};
HasRequiredKeys<Parameters> will resolve to "header" | "cookie", which is clearly not the intent of that helper type. However, with strictNullChecks enabled, it correctly resolves to "path".
The problem is most noticeable in the case mentioned in the original issue, when no parameters are defined:
type Parameters = {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
HasRequiredKeys<Parameters> resolves to "query" | "header" | "path" | "cookie", which then makes params a required property in the request init object.
I think we could fix this by replacing HasRequiredKeys with a helper type that doesn't rely on the inconsistent behavior of undefined extends T. I'm working on a PR that uses this helper instead:
type RequiredKeysOf<T> = {
[K in keyof T]: {} extends Pick<T, K> ? never : K;
}[keyof T];