-
-
Notifications
You must be signed in to change notification settings - Fork 282
/
client.ts
98 lines (88 loc) · 3.79 KB
/
client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import {TimeoutError, mapValues} from "@lodestar/utils";
import {compileRouteUrlFormater} from "../urlFormat.js";
import {RouteDef, ReqGeneric, ReturnTypes, TypeJson, ReqSerializer, ReqSerializers, RoutesData} from "../types.js";
import {APIClientHandler} from "../../interfaces.js";
import {FetchOpts, HttpError, IHttpClient} from "./httpClient.js";
import {HttpStatusCode} from "./httpStatusCode.js";
// See /packages/api/src/routes/index.ts for reasoning
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Format FetchFn opts from Fn arguments given a route definition and request serializer.
* For routes that return only JSOn use @see getGenericJsonClient
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function getFetchOptsSerializer<Fn extends (...args: any) => any, ReqType extends ReqGeneric>(
routeDef: RouteDef,
reqSerializer: ReqSerializer<Fn, ReqType>,
routeId: string
) {
const urlFormater = compileRouteUrlFormater(routeDef.url);
return function getFetchOpts(...args: Parameters<Fn>): FetchOpts {
const req = reqSerializer.writeReq(...args);
return {
url: urlFormater(req.params ?? {}),
method: routeDef.method,
query: req.query,
body: req.body as unknown,
headers: req.headers,
routeId,
};
};
}
/**
* Generate `getFetchOptsSerializer()` functions for all routes in `Api`
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function getFetchOptsSerializers<
Api extends Record<string, APIClientHandler>,
ReqTypes extends {[K in keyof Api]: ReqGeneric}
>(routesData: RoutesData<Api>, reqSerializers: ReqSerializers<Api, ReqTypes>) {
return mapValues(routesData, (routeDef, routeId) =>
getFetchOptsSerializer(routeDef, reqSerializers[routeId], routeId as string)
);
}
/**
* Get a generic JSON client from route definition, request serializer and return types.
*/
export function generateGenericJsonClient<
Api extends Record<string, APIClientHandler>,
ReqTypes extends {[K in keyof Api]: ReqGeneric}
>(
routesData: RoutesData<Api>,
reqSerializers: ReqSerializers<Api, ReqTypes>,
returnTypes: ReturnTypes<Api>,
fetchFn: IHttpClient
): Api {
return mapValues(routesData, (routeDef, routeId) => {
const fetchOptsSerializer = getFetchOptsSerializer(routeDef, reqSerializers[routeId], routeId as string);
const returnType = returnTypes[routeId as keyof ReturnTypes<Api>] as TypeJson<any> | null;
return async function request(...args: Parameters<Api[keyof Api]>): Promise<ReturnType<Api[keyof Api]>> {
try {
if (returnType) {
const res = await fetchFn.json<unknown>(fetchOptsSerializer(...args));
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return {ok: true, response: returnType.fromJson(res.body), status: res.status} as ReturnType<Api[keyof Api]>;
} else {
// We need to avoid parsing the response as the servers might just
// response status 200 and close the request instead of writing an
// empty json response. We return the status code.
const res = await fetchFn.request(fetchOptsSerializer(...args));
return {ok: true, response: undefined, status: res.status} as ReturnType<Api[keyof Api]>;
}
} catch (err) {
if (err instanceof HttpError) {
return {ok: false, error: {code: err.status, message: err.message, operationId: routeId}} as ReturnType<
Api[keyof Api]
>;
}
if (err instanceof TimeoutError) {
return {
ok: false,
error: {code: HttpStatusCode.INTERNAL_SERVER_ERROR, message: err.message, operationId: routeId},
} as ReturnType<Api[keyof Api]>;
}
throw err;
}
};
}) as unknown as Api;
}