Skip to content

Commit c51427a

Browse files
Disallow extra properties in swr-openapi init types
Co-authored-by: yashpandit <[email protected]>
1 parent 61c176c commit c51427a

File tree

5 files changed

+195
-27
lines changed

5 files changed

+195
-27
lines changed

.changeset/slimy-cows-jump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"swr-openapi": minor
3+
---
4+
5+
Disallow extra properties in swr-openapi init types

packages/swr-openapi/src/__test__/types.test-d.ts

Lines changed: 184 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,44 @@ describe("types", () => {
8585
useQuery("/pet/findByStatus", null);
8686
});
8787
});
88+
89+
describe("rejects extra properties", () => {
90+
it("in query params", () => {
91+
useQuery("/pet/findByStatus", {
92+
params: {
93+
query: {
94+
status: "available",
95+
// @ts-expect-error extra property should be rejected
96+
invalid_property: "nope",
97+
},
98+
},
99+
});
100+
});
101+
102+
it("in path params", () => {
103+
useQuery("/pet/{petId}", {
104+
params: {
105+
path: {
106+
petId: 5,
107+
// @ts-expect-error extra property should be rejected
108+
invalid_path_param: "nope",
109+
},
110+
},
111+
});
112+
});
113+
114+
it("in header params", () => {
115+
useQuery("/pet/findByStatus", {
116+
params: {
117+
header: {
118+
"X-Example": "test",
119+
// @ts-expect-error extra property should be rejected
120+
"Invalid-Header": "nope",
121+
},
122+
},
123+
});
124+
});
125+
});
88126
});
89127

90128
describe("useImmutable", () => {
@@ -122,6 +160,44 @@ describe("types", () => {
122160
useImmutable("/pet/findByStatus", null);
123161
});
124162
});
163+
164+
describe("rejects extra properties", () => {
165+
it("in query params", () => {
166+
useImmutable("/pet/findByStatus", {
167+
params: {
168+
query: {
169+
status: "available",
170+
// @ts-expect-error extra property should be rejected
171+
invalid_property: "nope",
172+
},
173+
},
174+
});
175+
});
176+
177+
it("in path params", () => {
178+
useImmutable("/pet/{petId}", {
179+
params: {
180+
path: {
181+
petId: 5,
182+
// @ts-expect-error extra property should be rejected
183+
invalid_path_param: "nope",
184+
},
185+
},
186+
});
187+
});
188+
189+
it("in header params", () => {
190+
useImmutable("/pet/findByStatus", {
191+
params: {
192+
header: {
193+
"X-Example": "test",
194+
// @ts-expect-error extra property should be rejected
195+
"Invalid-Header": "nope",
196+
},
197+
},
198+
});
199+
});
200+
});
125201
});
126202

127203
describe("useInfinite", () => {
@@ -154,40 +230,47 @@ describe("types", () => {
154230
useInfinite("/pet/findByStatus", () => null);
155231
});
156232
});
157-
});
158233

159-
describe("useMutate -> mutate", () => {
160-
it("accepts path alone", async () => {
161-
await mutate(["/pet/{petId}"]);
162-
});
234+
describe("rejects extra properties", () => {
235+
it("in query params", () => {
236+
useInfinite("/pet/findByStatus", () => ({
237+
params: {
238+
query: {
239+
status: "available",
240+
// @ts-expect-error extra property should be rejected
241+
invalid_property: "nope",
242+
},
243+
},
244+
}));
245+
});
163246

164-
it("accepts path and init", async () => {
165-
await mutate([
166-
"/pet/{petId}",
167-
{
247+
it("in path params", () => {
248+
useInfinite("/pet/{petId}", () => ({
168249
params: {
169250
path: {
170251
petId: 5,
252+
// @ts-expect-error extra property should be rejected
253+
invalid_path_param: "nope",
171254
},
172255
},
173-
},
174-
]);
175-
});
176-
177-
it("accepts partial init", async () => {
178-
await mutate(["/pet/{petId}", { params: {} }]);
179-
});
256+
}));
257+
});
180258

181-
it("does not accept `null` init", async () => {
182-
await mutate([
183-
"/pet/{petId}",
184-
// @ts-expect-error null not accepted
185-
null,
186-
]);
259+
it("in header params", () => {
260+
useInfinite("/pet/findByStatus", () => ({
261+
params: {
262+
header: {
263+
"X-Example": "test",
264+
// @ts-expect-error extra property should be rejected
265+
"Invalid-Header": "nope",
266+
},
267+
},
268+
}));
269+
});
187270
});
188271
});
189272

190-
describe("when init is not required", () => {
273+
describe("useMutate -> mutate", () => {
191274
it("accepts path alone", async () => {
192275
await mutate(["/pet/{petId}"]);
193276
});
@@ -216,6 +299,84 @@ describe("types", () => {
216299
null,
217300
]);
218301
});
302+
303+
describe("when init is not required", () => {
304+
it("accepts path alone", async () => {
305+
await mutate(["/pet/{petId}"]);
306+
});
307+
308+
it("accepts path and init", async () => {
309+
await mutate([
310+
"/pet/{petId}",
311+
{
312+
params: {
313+
path: {
314+
petId: 5,
315+
},
316+
},
317+
},
318+
]);
319+
});
320+
321+
it("accepts partial init", async () => {
322+
await mutate(["/pet/{petId}", { params: {} }]);
323+
});
324+
325+
it("does not accept `null` init", async () => {
326+
await mutate([
327+
"/pet/{petId}",
328+
// @ts-expect-error null not accepted
329+
null,
330+
]);
331+
});
332+
});
333+
334+
describe("rejects extra properties", () => {
335+
it("in path", () => {
336+
mutate([
337+
"/pet/{petId}",
338+
{
339+
params: {
340+
path: {
341+
petId: 5,
342+
// @ts-expect-error extra property should be rejected
343+
invalid_path_param: "no",
344+
},
345+
},
346+
},
347+
]);
348+
});
349+
350+
it("in query params", () => {
351+
mutate([
352+
"/pet/findByStatus",
353+
{
354+
params: {
355+
query: {
356+
status: "available",
357+
// @ts-expect-error extra property should be rejected
358+
invalid_property: "nope",
359+
},
360+
},
361+
},
362+
]);
363+
});
364+
365+
it("in header params", () => {
366+
mutate([
367+
"/pet/findByStatus",
368+
{
369+
params: {
370+
header: {
371+
"X-Example": "test",
372+
// @ts-expect-error extra property should be rejected
373+
"Invalid-Header": "nope",
374+
},
375+
},
376+
},
377+
]);
378+
});
379+
});
219380
});
220381
});
221382

packages/swr-openapi/src/infinite.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useSWRInfinite, {
77
} from "swr/infinite";
88
import type { TypesForGetRequest } from "./types.js";
99
import { useCallback, useDebugValue } from "react";
10+
import type { Exact } from "type-fest";
1011

1112
/**
1213
* Produces a typed wrapper for [`useSWRInfinite`](https://swr.vercel.app/docs/pagination#useswrinfinite).
@@ -42,7 +43,7 @@ export function createInfiniteHook<
4243
return function useInfinite<
4344
Path extends PathsWithMethod<Paths, "get">,
4445
R extends TypesForGetRequest<Paths, Path>,
45-
Init extends R["Init"],
46+
Init extends Exact<R["Init"], Init>,
4647
Data extends R["Data"],
4748
Error extends R["Error"] | FetcherError,
4849
Config extends SWRInfiniteConfiguration<Data, Error>,

packages/swr-openapi/src/mutate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Client } from "openapi-fetch";
22
import type { MediaType, PathsWithMethod } from "openapi-typescript-helpers";
33
import { useCallback, useDebugValue } from "react";
44
import { type MutatorCallback, type MutatorOptions, useSWRConfig } from "swr";
5-
import type { PartialDeep } from "type-fest";
5+
import type { Exact, PartialDeep } from "type-fest";
66
import type { TypesForGetRequest } from "./types.js";
77

88
// Types are loose here to support ecosystem utilities like `_.isMatch`
@@ -48,7 +48,7 @@ export function createMutateHook<Paths extends {}, IMediaType extends MediaType>
4848
function mutate<
4949
Path extends PathsWithMethod<Paths, "get">,
5050
R extends TypesForGetRequest<Paths, Path>,
51-
Init extends R["Init"],
51+
Init extends Exact<R["Init"], Init>,
5252
>(
5353
[path, init]: [Path, PartialDeep<Init>?],
5454
data?: R["Data"] | Promise<R["Data"]> | MutatorCallback<R["Data"]>,

packages/swr-openapi/src/query-base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescr
33
import type { Fetcher, SWRHook } from "swr";
44
import type { TypesForGetRequest } from "./types.js";
55
import { useCallback, useDebugValue, useMemo } from "react";
6+
import type { Exact } from "type-fest";
67

78
/**
89
* @private
@@ -17,7 +18,7 @@ export function configureBaseQueryHook(useHook: SWRHook) {
1718
return function useQuery<
1819
Path extends PathsWithMethod<Paths, "get">,
1920
R extends TypesForGetRequest<Paths, Path>,
20-
Init extends R["Init"],
21+
Init extends Exact<R["Init"], Init>,
2122
Data extends R["Data"],
2223
Error extends R["Error"] | FetcherError,
2324
Config extends R["SWRConfig"],

0 commit comments

Comments
 (0)