Undefined properties on body object are not flagged if required fields are filled in
Description
If I have something like this:
const { data, error } = await client.POST("/api/product", {
body: {
ProductVersion: "1.0.0",
ASDfadsfasdf: "asdfasdf"
}
});
then tsc correctly flags ASDfadsfasdf as being superfluous:
npx tsc --noEmit
test.ts:15:9 - error TS2353: Object literal may only specify known properties, and 'ASDfadsfasdf' does not exist in type '{ FileData: string; ProductVersion: string; }'.
15 ASDfadsfasdf: "asdfasdf"
However, as soon as all required fields are in:
const { data, error } = await client.POST("/api/product", {
body: {
ProductVersion: "1.0.0",
FileData: "test",
ASDfadsfasdf: "asdfasdf"
}
});
it is no longer flagged:
npx tsc --noEmit
This is a problem because it means you can introduce typos on optional fields, and tsc won't catch it.
Reproduction
Seems to happen no matter what I try. tsconfig is set to esnext module with bundler resolution.
Expected result
Superfluous fields should be flagged regardless of if required ones are filled in or not.
Checklist
- [ ] I’m willing to open a PR (see CONTRIBUTING.md)
Ok, I think I've figured out more clues as to what's causing this. It doesn't necessarily have to do with required properties. If multiple components have similar properties (such as id), the type checking only works if you've defined enough properties to uniquely identify the component.
So for example, this is not flagged nor does jump to definition work because id alone doesn't uniquely identify the component:
but this is correctly flagged, and jump to definition is working again, because status uniquely identifies the Order component:
Also note that results are the same in the above 2 cases when doing npx tsc --noEmit, so I know it's not just a quirk of VSCode
My guess is that this is some quirk of generics. The end result is that the type-checking is unreliable because you can now have silent failures in the cases where you haven't defined enough properties to uniquely identify the component.
Hello. I'm gonna fix your bug. Can you tell me about your bug?
First option: Using object spread can help catch extra properties!
const baseBody = {
ProductVersion: "1.0.0",
FileData: "test"
};
// Add an extra property with a spread
const body = {
...baseBody,
ASDfadsfasdf: "asdfasdf" // TypeScript will flag this
};
const { data, error } = await client.POST("/api/product", { body });
Second option: Applying explicit type annotations forces TypeScript to perform a strict check!
const body: { FileData: string; ProductVersion: string } = {
ProductVersion: "1.0.0",
FileData: "test",
ASDfadsfasdf: "asdfasdf" // This will now be flagged
};
const { data, error } = await client.POST("/api/product", { body });
Thanks, but it appears these workarounds still don't catch the issue. For example:
import createClient from "openapi-fetch";
import type { paths } from "./petstore"; // generated by openapi-typescript
const client = createClient<paths>();
const baseBody: { id: number, bogus: string } = {
id: 0,
bogus: "bogus",
};
const {
data, // only present if 2XX response
error, // only present if 4XX or 5XX response
} = await client.POST("/store/order", {
params: {},
body: baseBody,
});
The bogus field does not get flagged by tsc
@Elgueromiranda That defeats the purpose of openapi-typescript if I need to re-define the body types.
Bitten by this today. I had a typo in an optional body parameter name that went completely unchecked.
This is a great catch, and would welcome a PR for this. One suggestion is to move all type tests into a *.test-d-ts type test and make use of Vitest’s type assertion utilities. There are other PRs of related type bugs open (like with newer TS) that need to be solved mostly with stricter typechecks than we have in the runtime tests. That’s not a hard requirement for solving this, but it would greatly help.
I spent several hours trying to figure this one out because I really want it fixed. The problem I've decided is that TypeScript does not have good tooling for debugging complex types like this. I can't, for example, set a breakpoint in a debugger and step through how types are propagating through generics (if such a tool exists, let me know). So it's just a lot of trial and error and it's a nightmarish debugging experience
I got so frustrated that over the weekend I made an alternate implementation of openapi-fetch that uses code generation instead of generics like I alluded to in #1779 - I'm calling it typed-fetch. Should be API compatible with openapi-fetch minus some of the extra stuff openapi-fetch has that native fetch does not have (like middlewares). I'm sure it's missing a lot of corner cases, but so far it's pretty solid with swagger petstore and doesn't seem to suffer from weird issues like this library does. Wanted to throw it out there because I think if you were to copy the architecture (swapping out golang for TypeScript), the simplicity of it would make openapi-fetch much easier to contribute to, debug, test, and maintain because you can use a real debugger.