Skip to content

Commit cf1abd5

Browse files
authored
fix: Merge pull request #5 from seamapi/fix-path-param-parsing
2 parents ce6417f + 09db7aa commit cf1abd5

File tree

3 files changed

+81
-38
lines changed

3 files changed

+81
-38
lines changed

src/index.ts

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { AxiosResponse, AxiosRequestConfig, AxiosInstance } from "axios"
2-
import type { SetOptional, Except, Simplify } from "type-fest"
2+
import type { SetOptional, Except, Simplify, Split } from "type-fest"
33

44
export type HTTPMethod =
55
| "GET"
@@ -29,6 +29,20 @@ export type APIDefToUnion<Routes extends APIDef> = Routes extends RouteDef[]
2929
? Routes[keyof Routes]
3030
: never
3131

32+
// Given a union of {method: "GET" | "POST", ...} | {method: "PATCH", ...} converts it to a union of
33+
// {method: "GET", ...} | {method: "POST", ...} | {method: "PATCH", ...}
34+
export type ExplodeMethodsOnRouteDef<Route extends RouteDef> = Route extends any
35+
? Route extends { method: infer Method }
36+
? Method extends HTTPMethod
37+
? Simplify<
38+
{
39+
method: Method
40+
} & Except<Route, "method">
41+
>
42+
: never
43+
: never
44+
: never
45+
3246
// Converts from `/things/[thing_id]/get` to `/things/${string}/get`
3347
export type ReplacePathParams<Path extends string> =
3448
Path extends `${infer Before}[${infer Param}]${infer After}`
@@ -37,40 +51,31 @@ export type ReplacePathParams<Path extends string> =
3751

3852
// Converts from `/things/example_thing_id/get` to `/things/${string}/get`
3953
export type WidenConcretePathParams<
40-
Path extends string,
54+
Path extends AnyRoutePath<Route>,
4155
Route extends RouteDef
42-
> = Path extends ReplacePathParams<Route["route"]>
43-
? ReplacePathParams<Route["route"]>
56+
> = Route extends any
57+
? Route extends { route: infer WidenedPath }
58+
? Path extends WidenedPath
59+
? WidenedPath
60+
: never
61+
: never
4462
: never
4563

46-
export type ReplacePathParamsOnRouteDef<Route extends RouteDef> = Simplify<
47-
{
48-
route: ReplacePathParams<Route["route"]>
49-
} & Except<Route, "route">
50-
>
64+
export type ReplacePathParamsOnRouteDef<Route extends RouteDef> =
65+
Route extends any
66+
? Simplify<
67+
{
68+
route: ReplacePathParams<Route["route"]>
69+
} & Except<Route, "route">
70+
>
71+
: never
5172

5273
export type AnyRoutePath<Routes extends RouteDef> = Routes["route"]
5374

54-
// Given a path and a method, widen the method type to all methods accepted for that path
55-
export type WidenConcreteMethod<
56-
Routes extends RouteDef,
57-
Path extends AnyRoutePath<Routes>,
58-
Method extends HTTPMethod
59-
> = Extract<Routes, { route: Path }> extends infer Route
60-
? Route extends RouteDef
61-
? Method extends Route["method"]
62-
? Route["method"]
63-
: never
64-
: never
65-
: never
66-
6775
export type PathWithMethod<
6876
Routes extends RouteDef,
6977
Method extends HTTPMethod
70-
> = Extract<
71-
Routes,
72-
{ method: WidenConcreteMethod<Routes, AnyRoutePath<Routes>, Method> }
73-
>["route"]
78+
> = Extract<Routes, { method: Method }>["route"]
7479

7580
export type MatchingRouteByPath<
7681
Routes extends RouteDef,
@@ -87,17 +92,15 @@ export type MatchingRoute<
8792
Routes extends RouteDef,
8893
Path extends AnyRoutePath<Routes>,
8994
Method extends HTTPMethod = HTTPMethod
90-
> = Extract<
91-
Routes,
92-
{
93-
route: WidenConcretePathParams<Path, Routes>
94-
method: WidenConcreteMethod<
95-
Routes,
96-
WidenConcretePathParams<Path, Routes>,
97-
Method
98-
>
99-
}
100-
>
95+
> = Routes extends infer Route
96+
? Route extends RouteDef
97+
? Split<Path, "/"> extends Split<Route["route"], "/">
98+
? Route["method"] extends Method
99+
? Route
100+
: never
101+
: never
102+
: never
103+
: never
101104

102105
export type RouteResponse<
103106
Routes extends RouteDef,
@@ -141,7 +144,9 @@ export type ExtendedAxiosRequestConfigForMethod<
141144

142145
export interface TypedAxios<
143146
T extends APIDef,
144-
Routes extends RouteDef = ReplacePathParamsOnRouteDef<APIDefToUnion<T>>
147+
Routes extends RouteDef = ExplodeMethodsOnRouteDef<
148+
ReplacePathParamsOnRouteDef<APIDefToUnion<T>>
149+
>
145150
> {
146151
defaults: AxiosInstance["defaults"]
147152
interceptors: AxiosInstance["interceptors"]

tests/example-route-types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ export type ExampleRouteTypes3 = {
7676
}
7777

7878
export type ExampleRouteTypes4 = {
79+
"/things/[thing_id]": {
80+
route: "/things/[thing_id]"
81+
method: "GET"
82+
queryParams: {}
83+
commonParams: {}
84+
formData: {}
85+
jsonResponse: {
86+
thing_get: {
87+
thing_id: string
88+
name: string
89+
created_at: string
90+
}
91+
}
92+
}
7993
"/things/[thing_id]/get": {
8094
route: "/things/[thing_id]/get"
8195
method: "POST" | "GET"
@@ -90,4 +104,18 @@ export type ExampleRouteTypes4 = {
90104
}
91105
}
92106
}
107+
"/things/[thing_id]/update": {
108+
route: "/things/[thing_id]/update"
109+
method: "POST"
110+
queryParams: {}
111+
commonParams: {}
112+
formData: {}
113+
jsonResponse: {
114+
thing_update: {
115+
thing_id: string
116+
name: string
117+
created_at: string
118+
}
119+
}
120+
}
93121
}

tests/main.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,14 @@ test("parses path parameters", async (t) => {
6262
created_at: string
6363
}
6464
}>()
65+
66+
const topRes = await axios.get("/things/10")
67+
68+
expectTypeOf(topRes.data).toMatchTypeOf<{
69+
thing_get: {
70+
thing_id: string
71+
name: string
72+
created_at: string
73+
}
74+
}>()
6575
})

0 commit comments

Comments
 (0)