Skip to content

Commit db79ddd

Browse files
authored
queryが定義されているかに応じてInputの型を絞り込む (#81)
1 parent 2d8a451 commit db79ddd

File tree

4 files changed

+53
-22
lines changed

4 files changed

+53
-22
lines changed

Diff for: examples/express/fetch.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const JSONT = JSON as JSONT;
1010

1111
const main = async () => {
1212
{
13-
const path = `${origin}/users`;
13+
const path = `${origin}/users?page=1`;
1414
const method = "get";
1515
const res = await fetchT(path, { method });
1616
switch (res.status) {
@@ -32,7 +32,7 @@ const main = async () => {
3232
}
3333
{
3434
// case-insensitive method example
35-
await fetchT(`${origin}/users`, { method: "GET" });
35+
await fetchT(`${origin}/users?page=1`, { method: "GET" });
3636
}
3737
{
3838
// query parameter example

Diff for: src/common/spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ export type ApiP<
9595
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
9696
E[Path][M][P] extends Record<string, any>
9797
? E[Path][M][P]
98-
: never
99-
: never
100-
: never;
98+
: undefined
99+
: undefined
100+
: undefined;
101101

102102
export type ApiHasP<
103103
E extends ApiEndpoints,

Diff for: src/fetch/index.t-test.ts

+25-12
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@ import JSONT from "../json";
8484
}
8585

8686
{
87-
// AsJsonApiを利用していない場合、Content-Typeがapplication/jsonでなくてもエラーにならない
88-
await f2("/users", {});
89-
}
90-
91-
{
87+
// @ts-expect-error queryが定義されていないSpecに対してクエリパラメータを指定した場合は型エラー
9288
await f("/users?a=1", {
9389
headers: { "Content-Type": "application/json" },
9490
});
9591
}
9692

93+
{
94+
// AsJsonApiを利用していない場合、Content-Typeがapplication/jsonでなくてもエラーにならない
95+
await f2("/users", {});
96+
}
97+
9798
{
9899
const res = await f("/users", {
99100
method: "post",
@@ -155,20 +156,32 @@ import JSONT from "../json";
155156
resBody: {
156157
200: { prop: string };
157158
};
159+
query: {
160+
state: boolean;
161+
};
158162
};
159163
};
160164
}>;
161165
(async () => {
162166
const basePath = "/api/projects/:projectName/workflow";
163167
const f = fetch as FetchT<typeof basePath, Spec>;
164-
const res = await f(
165-
`/api/projects/projectA/workflow/packages/list?state=true`,
168+
{
169+
const res = await f(
170+
`/api/projects/projectA/workflow/packages/list?state=true`,
171+
{
172+
headers: { Cookie: "a=b" },
173+
},
174+
);
175+
if (res.ok) {
176+
(await res.json()).prop;
177+
}
178+
166179
{
167-
headers: { Cookie: "a=b" },
168-
},
169-
);
170-
if (res.ok) {
171-
(await res.json()).prop;
180+
// @ts-expect-error queryが定義されているSpecに対してクエリパラメータを指定しなかった場合は型エラー
181+
f(`/api/projects/projectA/workflow/packages/list`, {
182+
headers: { Cookie: "a=b" },
183+
});
184+
}
172185
}
173186
})();
174187
}

Diff for: src/fetch/index.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
ApiEndpoints,
33
ApiHasP,
44
ApiP,
5+
ApiResponses,
56
CaseInsensitiveMethod,
67
FilterNever,
78
MatchedPatterns,
@@ -11,14 +12,15 @@ import {
1112
ParseURL,
1213
PathToUrlParamPattern,
1314
Replace,
15+
StatusCode,
1416
} from "../common";
1517
import { UrlPrefixPattern, ToUrlParamPattern } from "../common";
1618
import { TypedString } from "../json";
1719

1820
export type RequestInitT<
1921
InputMethod extends CaseInsensitiveMethod,
2022
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21-
Body extends Record<string, any> | never,
23+
Body extends Record<string, any> | undefined,
2224
HeadersObj extends Record<string, string> | undefined,
2325
> = Omit<RequestInit, "method" | "body" | "headers"> & {
2426
method?: InputMethod;
@@ -34,9 +36,9 @@ export type RequestInitT<
3436
* FetchT is a type for window.fetch like function but more strict type information
3537
*/
3638
type FetchT<UrlPrefix extends UrlPrefixPattern, E extends ApiEndpoints> = <
37-
Input extends
38-
| ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>
39-
| `${ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>}?${string}`,
39+
Input extends Query extends undefined
40+
? ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>
41+
: `${ToUrlParamPattern<`${UrlPrefix}${keyof E & string}`>}?${string}`,
4042
InputPath extends PathToUrlParamPattern<
4143
NormalizePath<
4244
ParseURL<Replace<Input, ToUrlParamPattern<UrlPrefix>, "">>["path"]
@@ -45,6 +47,18 @@ type FetchT<UrlPrefix extends UrlPrefixPattern, E extends ApiEndpoints> = <
4547
CandidatePaths extends string = MatchedPatterns<InputPath, keyof E & string>,
4648
InputMethod extends CaseInsensitiveMethod = "get",
4749
M extends Method = Lowercase<InputMethod>,
50+
Query extends ApiP<E, CandidatePaths, M, "query"> = ApiP<
51+
E,
52+
CandidatePaths,
53+
M,
54+
"query"
55+
>,
56+
ResBody extends ApiP<E, CandidatePaths, M, "resBody"> = ApiP<
57+
E,
58+
CandidatePaths,
59+
M,
60+
"resBody"
61+
>,
4862
>(
4963
input: Input,
5064
init: ApiHasP<E, CandidatePaths, M> extends true
@@ -60,6 +74,10 @@ type FetchT<UrlPrefix extends UrlPrefixPattern, E extends ApiEndpoints> = <
6074
ApiP<E, CandidatePaths, M, "headers">
6175
>
6276
| undefined,
63-
) => Promise<MergeApiResponses<ApiP<E, CandidatePaths, M, "resBody">>>;
77+
) => Promise<
78+
MergeApiResponses<
79+
ResBody extends ApiResponses ? ResBody : Record<StatusCode, never>
80+
>
81+
>;
6482

6583
export default FetchT;

0 commit comments

Comments
 (0)