Skip to content

Commit e653f9b

Browse files
authored
Fix parse url params and add comments (#22)
* Refine types * Organize type test * Add comments to type * Add more comments
1 parent 2b5a5b7 commit e653f9b

File tree

8 files changed

+358
-54
lines changed

8 files changed

+358
-54
lines changed

src/common/spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const Method = [
1212
] as const;
1313
export type Method = (typeof Method)[number];
1414

15-
export type ApiEndpoint<Path> = Partial<
15+
export type ApiEndpoint<Path extends string> = Partial<
1616
Record<Method, ApiSpec<ParseUrlParams<Path>>>
1717
>;
1818
export type ApiEndpoints = {

src/common/type.t-test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Equal, Expect } from "./type-test";
2+
import { ExtractByPrefix, FilterNever, Replace, Split } from "./type";
3+
4+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
5+
type FilterNeverCases = [
6+
// eslint-disable-next-line @typescript-eslint/ban-types
7+
Expect<Equal<FilterNever<{ a: never }>, {}>>,
8+
Expect<Equal<FilterNever<{ a: never; b: string }>, { b: string }>>,
9+
];
10+
11+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
12+
type SplitTestCases = [
13+
Expect<Equal<Split<"", "">, []>>,
14+
Expect<Equal<Split<"a", "">, ["a"]>>,
15+
Expect<Equal<Split<"ab", "">, ["a", "b"]>>,
16+
Expect<Equal<Split<"a/b", "/">, ["a", "b"]>>,
17+
Expect<Equal<Split<"/a/b", "/">, ["", "a", "b"]>>,
18+
Expect<Equal<Split<"a/b/c", "/">, ["a", "b", "c"]>>,
19+
Expect<Equal<Split<"a/b/", "/">, ["a", "b", ""]>>,
20+
];
21+
22+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
23+
type ReplaceTestCases = [
24+
Expect<Equal<Replace<"a", "a", "-">, "-">>,
25+
Expect<Equal<Replace<"a", "noexist", "-">, "a">>,
26+
Expect<Equal<Replace<"a", "a", "a">, "a">>,
27+
Expect<Equal<Replace<"abcd", "ab", "-">, "-cd">>,
28+
Expect<Equal<Replace<"abcd", "cd", "-">, "ab-">>,
29+
Expect<Equal<Replace<"abcd", "bc", "-">, "a-d">>,
30+
];
31+
32+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
33+
type ExtractByPrefixTestCases = [
34+
Expect<Equal<ExtractByPrefix<"", "">, "">>,
35+
Expect<Equal<ExtractByPrefix<"a", "">, "a">>,
36+
Expect<Equal<ExtractByPrefix<"a" | "b", "">, "a" | "b">>,
37+
Expect<Equal<ExtractByPrefix<"a", ":">, never>>,
38+
Expect<Equal<ExtractByPrefix<":a", ":">, "a">>,
39+
Expect<Equal<ExtractByPrefix<":a" | "b", ":">, "a">>,
40+
Expect<Equal<ExtractByPrefix<"a" | ":b", ":">, "b">>,
41+
Expect<Equal<ExtractByPrefix<":a" | ":b", ":">, "a" | "b">>,
42+
Expect<Equal<ExtractByPrefix<":a" | ":b" | ":c", ":">, "a" | "b" | "c">>,
43+
];

src/common/type.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,66 @@
1+
/**
2+
* Filter key & value, which has "never" value
3+
*
4+
* @example
5+
* ```
6+
* type T0 = FilterNever<{ a: never; b: string }>, { b: string }>
7+
* // => {b: string}
8+
* ```
9+
*/
110
export type FilterNever<T extends Record<string, unknown>> = {
211
[K in keyof T as T[K] extends never ? never : K]: T[K];
312
};
413

14+
/**
15+
* Replace substring
16+
* S: source string
17+
* From: substring to be replaced
18+
* To: substring to replace
19+
*
20+
* @example
21+
* ```
22+
* type T0 = Replace<"abcd", "bc", "-">;
23+
* // => "a-d"
24+
* ```
25+
*/
526
export type Replace<
627
S extends string,
728
From extends string,
829
To extends string,
930
> = S extends `${infer P}${From}${infer R}` ? `${P}${To}${R}` : S;
31+
32+
/**
33+
* Split string by delimiter
34+
* S: source string
35+
* Delimiter: delimiter to split
36+
*
37+
* @example
38+
* ```
39+
* type T0 = Split<"a/b/c", "/">;
40+
* // => ["a", "b", "c"]
41+
* ```
42+
*/
43+
export type Split<
44+
S extends string,
45+
Delimiter extends string,
46+
> = S extends `${infer Head}${Delimiter}${infer Tail}`
47+
? [Head, ...Split<Tail, Delimiter>]
48+
: S extends Delimiter
49+
? []
50+
: [S];
51+
52+
/**
53+
* Extract string by prefix
54+
* T: source string
55+
* Prefix: prefix to extract
56+
*
57+
* @example
58+
* ```
59+
* type T0 = ExtractByPrefix<"p-a" | "p-b" | "c", "p-">;
60+
* // => "a" | "b"
61+
* ```
62+
*/
63+
export type ExtractByPrefix<
64+
T extends string,
65+
Prefix extends string,
66+
> = T extends `${Prefix}${infer R}` ? R : never;

src/common/url.t-test.ts

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,145 @@
11
import { Equal, Expect } from "./type-test";
2-
import { ParseOrigin, ParseURL } from "./url";
2+
import {
3+
MatchedPatterns,
4+
ParseHostAndPort,
5+
ParseOriginAndPath,
6+
ParseURL,
7+
ParseUrlParams,
8+
ToUrlParamPattern,
9+
ToUrlPattern,
10+
} from "./url";
311

412
// eslint-disable-next-line @typescript-eslint/no-unused-vars
5-
type cases = [
6-
Expect<Equal<ParseOrigin<undefined>, never>>,
13+
type ParseUrlParamsTestCases = [
14+
// @ts-expect-error undefined is not a string
15+
Expect<Equal<ParseUrlParams<undefined>, never>>,
16+
Expect<Equal<ParseUrlParams<"">, never>>,
17+
Expect<Equal<ParseUrlParams<"">, never>>,
18+
Expect<Equal<ParseUrlParams<":a">, "a">>,
19+
Expect<Equal<ParseUrlParams<"/:a">, "a">>,
20+
Expect<Equal<ParseUrlParams<"/:a/:b">, "a" | "b">>,
21+
Expect<Equal<ParseUrlParams<"/a/:b">, "b">>,
22+
];
23+
24+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
25+
type ToUrlParamPatternTestCases = [
26+
Expect<Equal<ToUrlParamPattern<"">, "">>,
27+
Expect<Equal<ToUrlParamPattern<"/">, "/">>,
28+
Expect<Equal<ToUrlParamPattern<":a">, string>>,
29+
Expect<Equal<ToUrlParamPattern<"/:a/b">, `/${string}/b`>>,
30+
Expect<Equal<ToUrlParamPattern<"/:a/:b">, `/${string}/${string}`>>,
31+
Expect<
32+
// @ts-expect-error URL is not supported
33+
Equal<ToUrlParamPattern<"https://example.com">, `"https://example.com}`>
34+
>,
35+
];
36+
37+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
38+
type ToUrlPatternTestCases = [
39+
Expect<Equal<ToUrlPattern<"">, "">>,
40+
Expect<Equal<ToUrlPattern<"/">, "/">>,
41+
Expect<Equal<ToUrlPattern<"/users/:userId">, `/users/${string}`>>,
742
Expect<
843
Equal<
9-
ParseOrigin<"">,
10-
{ schema: undefined; host: undefined; port: undefined; path: "" }
44+
ToUrlPattern<"/users/:userId?key=value">,
45+
`/users/${string}?key=value`
1146
>
1247
>,
48+
// @ts-expect-error URL is not supported
49+
Expect<Equal<ToUrlPattern<"https://example.com">, "https://example.com">>,
50+
];
51+
52+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
53+
type MatchedPatternsTestCases = [
54+
Expect<Equal<MatchedPatterns<"", "">, "">>,
55+
Expect<Equal<MatchedPatterns<"/1", "/:userId">, "/:userId">>,
56+
Expect<
57+
Equal<MatchedPatterns<"/1", "/:userId" | "/:orgId">, "/:userId" | "/:orgId">
58+
>,
59+
Expect<
60+
Equal<
61+
MatchedPatterns<"/users/1", "/users/:userId" | "/:userId">,
62+
"/users/:userId" | "/:userId"
63+
>
64+
>,
65+
Expect<
66+
Equal<
67+
MatchedPatterns<"/users/1", "/users/:userId" | "/org/:orgId">,
68+
"/users/:userId"
69+
>
70+
>,
71+
];
72+
73+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
74+
type ParseHostAndPortTestCases = [
1375
Expect<
1476
Equal<
15-
ParseOrigin<"https://example.com">,
16-
{ host: "example.com"; port: undefined } & { schema: "https"; path: "" }
77+
ParseHostAndPort<"example.com">,
78+
{ host: "example.com"; port: undefined }
1779
>
1880
>,
1981
Expect<
2082
Equal<
21-
ParseOrigin<"https://example.com/">,
83+
ParseHostAndPort<"example.com:8080">,
84+
{ host: "example.com"; port: "8080" }
85+
>
86+
>,
87+
// If invalid port is specified, it should return never
88+
Expect<Equal<ParseHostAndPort<"example.com:xxx">, never>>,
89+
];
90+
91+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
92+
type ParseOriginAndPathCases = [
93+
Expect<Equal<ParseOriginAndPath<undefined>, never>>,
94+
Expect<
95+
Equal<
96+
ParseOriginAndPath<"">,
97+
{ schema: undefined; host: undefined; port: undefined; path: "" }
98+
>
99+
>,
100+
Expect<
101+
Equal<
102+
ParseOriginAndPath<"https://example.com/">,
22103
{ host: "example.com"; port: undefined } & { schema: "https"; path: "/" }
23104
>
24105
>,
25106
Expect<
26107
Equal<
27-
ParseOrigin<"https://example.com/user">,
108+
ParseOriginAndPath<"https://example.com/user">,
28109
{ host: "example.com"; port: undefined } & {
29110
schema: "https";
30111
path: "/user";
31112
}
32113
>
33114
>,
115+
116+
Expect<
117+
Equal<
118+
ParseOriginAndPath<"https://example.com/users/:userId">,
119+
{ host: "example.com"; port: undefined } & {
120+
schema: "https";
121+
path: "/users/:userId";
122+
}
123+
>
124+
>,
125+
34126
Expect<
35127
Equal<
36-
ParseOrigin<"https://example.com:8080/user">,
128+
ParseOriginAndPath<"https://example.com:8080/user">,
37129
{ host: "example.com"; port: "8080" } & { schema: "https"; path: "/user" }
38130
>
39131
>,
132+
40133
Expect<
41134
Equal<
42-
ParseOrigin<"/user">,
135+
ParseOriginAndPath<"/user">,
43136
{ schema: undefined; host: undefined; port: undefined; path: "/user" }
44137
>
45138
>,
139+
];
46140

47-
Expect<Equal<ParseURL<"/user">["path"], "/user">>,
141+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
142+
type ParseURLTestCases = [
143+
Expect<Equal<ParseURL<"/user?a=b">["path"], "/user">>,
48144
Expect<Equal<ParseURL<"https://example.com/user">["path"], "/user">>,
49145
];

0 commit comments

Comments
 (0)