Skip to content

Commit

Permalink
Use composite reponse type from the server handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarhussain committed Jul 28, 2023
1 parent a127714 commit e1cbec4
Show file tree
Hide file tree
Showing 16 changed files with 80 additions and 44 deletions.
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
16 changes: 9 additions & 7 deletions packages/api/src/beacon/server/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();

Expand All @@ -21,23 +21,25 @@ export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerR
...serverRoutes.getState,
handler: async (req) => {
const response = await api.getState(...reqSerializers.getState.parseReq(req));
if (response instanceof Uint8Array) {
const res = "status" in response ? response.response : response;
if (res instanceof Uint8Array) {
// Fastify 3.x.x will automatically add header `Content-Type: application/octet-stream` if Buffer
return Buffer.from(response);
return Buffer.from(res);
} else {
return returnTypes.getState.toJson(response);
return returnTypes.getState.toJson(res);
}
},
},
getStateV2: {
...serverRoutes.getStateV2,
handler: async (req) => {
const response = await api.getStateV2(...reqSerializers.getStateV2.parseReq(req));
if (response instanceof Uint8Array) {
const res = "status" in response ? response.response : response;
if (res instanceof Uint8Array) {
// Fastify 3.x.x will automatically add header `Content-Type: application/octet-stream` if Buffer
return Buffer.from(response);
return Buffer.from(res);
} else {
return returnTypes.getStateV2.toJson(response);
return returnTypes.getStateV2.toJson(res);
}
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Api, ReqTypes, routesData, getEventSerdes} from "../routes/events.js";
import {ServerRoutes} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
const eventSerdes = getEventSerdes(config);

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/lightclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/lodestar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
6 changes: 4 additions & 2 deletions packages/api/src/beacon/server/proof.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerR
...serverRoutes.getStateProof,
handler: async (req) => {
const args = reqSerializers.getStateProof.parseReq(req);
const {data} = await api.getStateProof(...args);
const apiRes = await api.getStateProof(...args);
const data = "status" in apiRes ? apiRes.response?.data : apiRes.data;
const leaves = (data as CompactMultiProof).leaves;
const response = new Uint8Array(32 * leaves.length);
for (let i = 0; i < leaves.length; i++) {
Expand All @@ -32,7 +33,8 @@ export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerR
...serverRoutes.getBlockProof,
handler: async (req) => {
const args = reqSerializers.getBlockProof.parseReq(req);
const {data} = await api.getBlockProof(...args);
const apiRes = await api.getBlockProof(...args);
const data = "status" in apiRes ? apiRes.response?.data : apiRes.data;
const leaves = (data as CompactMultiProof).leaves;
const response = new Uint8Array(32 * leaves.length);
for (let i = 0; i < leaves.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/server/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js";
import {ServerApi} from "../../interfaces.js";

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
2 changes: 1 addition & 1 deletion packages/api/src/builder/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
// Re-export for convenience
export {RouteConfig};

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
Expand Down
32 changes: 25 additions & 7 deletions packages/api/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {Resolves} from "./utils/types.js";
/* eslint-disable @typescript-eslint/no-explicit-any */

export type APIClientHandler = (...args: any) => PromiseLike<ApiClientResponse>;
export type APIServerHandler = (...args: any) => PromiseLike<unknown>;

export type APIServerHandler = (...args: any) => PromiseLike<ApiServerResponse<ApiClientResponse>>;
export type ApiClientSuccessResponse<S extends keyof any, T> = {ok: true; status: S; response: T; error?: never};
export type ApiClientErrorResponse<S extends Exclude<HttpStatusCode, HttpSuccessCodes>> = {
ok: false;
Expand All @@ -21,17 +20,36 @@ export type ApiClientResponse<
| {[K in E]: ApiClientErrorResponse<K>}[E]
| ApiClientErrorResponse<HttpStatusCode.INTERNAL_SERVER_ERROR>;

export type ApiClientResponseData<T extends ApiClientResponse> = T extends {ok: true; response: infer R} ? R : never;
export type ApiServerResponse<T extends ApiClientResponse> = T extends {
ok: true;
response: infer R;
status: infer S;
}
? R | {status: S; response: R}
: never;

export type ApiClientResolvesData<T extends ApiClientResponse> = T extends {ok: true; response: infer R} ? R : never;
export type ApiServerResolvesData<T extends ApiServerResponse<ApiClientResponse>> = T extends void
? never
: T extends {
status: number;
response: infer R;
}
? R
: never;

export type GenericRequestObject = Record<string, unknown>;
export type GenericResponseObject = {code: (code: number) => void};

export type ServerApi<T extends Record<string, APIClientHandler>> = {
[K in keyof T]: (
...args: [...args: Parameters<T[K]>, req?: GenericRequestObject, res?: GenericResponseObject]
) => Promise<ApiClientResponseData<Resolves<T[K]>>>;
...args: [...args: Parameters<T[K]>, req?: GenericRequestObject]
) => Promise<ApiServerResponse<Resolves<T[K]>>>;
};

export type ClientApi<T extends Record<string, APIServerHandler>> = {
[K in keyof T]: (...args: Parameters<T[K]>) => Promise<ApiClientResponse<{[HttpStatusCode.OK]: Resolves<T[K]>}>>;
[K in keyof T]: ApiServerResolvesData<Resolves<T[K]>> extends void
? never
: (...args: Parameters<T[K]>) => Promise<ApiClientResponse<{[HttpStatusCode.OK]: Resolves<T[K]>}>>;
};

type T = ApiServerResponse<ApiClientResponse<{[HttpStatusCode.OK]: boolean}>>;
2 changes: 1 addition & 1 deletion packages/api/src/keymanager/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../r
// Re-export for convenience
export {RouteConfig};

export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<Api, ReqTypes> {
export function getRoutes(config: ChainForkConfig, api: ServerApi<Api>): ServerRoutes<ServerApi<Api>, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<ServerApi<Api>, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
Expand Down
26 changes: 20 additions & 6 deletions packages/api/src/utils/server/genericJsonServer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type {FastifyInstance} from "fastify";
import {mapValues} from "@lodestar/utils";
import {ChainForkConfig} from "@lodestar/config";
import {ReqGeneric, TypeJson, Resolves, RouteGroupDefinition} from "../types.js";
import {ReqGeneric, TypeJson, RouteGroupDefinition} from "../types.js";
import {getFastifySchema} from "../schema.js";
import {toColonNotationPath} from "../urlFormat.js";
import {APIServerHandler} from "../../interfaces.js";
import {HttpStatusCode} from "../client/httpStatusCode.js";
import {ServerRoute} from "./types.js";

// See /packages/api/src/routes/index.ts for reasoning
Expand Down Expand Up @@ -42,14 +43,27 @@ export function getGenericJsonServer<

handler: async function handler(this: FastifyInstance, req, resp): Promise<unknown | void> {
const args: any[] = routeSerdes.parseReq(req as ReqGeneric as ReqTypes[keyof Api]);
const data = (await api[routeId](...args, req, resp)) as Resolves<Api[keyof Api]>;

if (routeDef.statusOk !== undefined) {
resp.statusCode = routeDef.statusOk;
}
// The type resolves here is `unknown | void | {status: number; response: unknown | void}`
// which end up being just `unknown` in the end because of the `unknown | void` part
const data = await api[routeId](...args, req, resp);

const status =
"status" in (data as {status: number; response: unknown})
? (data as {status: number; response: unknown}).status
: routeDef.statusOk !== undefined
? routeDef.statusOk
: HttpStatusCode.OK;

const response =
"status" in (data as {status: number; response: unknown})
? (data as {status: number; response: unknown}).response
: data;

resp.statusCode = status;

if (returnType) {
return returnType.toJson(data);
return returnType.toJson(response);
} else {
return {};
}
Expand Down
7 changes: 4 additions & 3 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {isBasicType, ListBasicType, Type, isCompositeType, ListCompositeType, Ar
import {ForkName} from "@lodestar/params";
import {ChainForkConfig} from "@lodestar/config";
import {objectToExpectedCase} from "@lodestar/utils";
import {APIClientHandler, ApiClientResponseData, APIServerHandler, ClientApi} from "../interfaces.js";
import {APIClientHandler, ApiClientResolvesData, ApiServerResponse} from "../interfaces.js";
import {APIServerHandler, ClientApi} from "../interfaces.js";
import {Schema, SchemaDefinition} from "./schema.js";

// See /packages/api/src/routes/index.ts for reasoning
Expand Down Expand Up @@ -219,11 +220,11 @@ export function sameType<T>(): TypeJson<T> {
// RETURN
//
export type KeysOfNonVoidResolveValues<Api extends Record<string, APIClientHandler>> = {
[K in keyof Api]: ApiClientResponseData<Resolves<Api[K]>> extends void ? never : K;
[K in keyof Api]: ApiClientResolvesData<Resolves<Api[K]>> extends void ? never : K;
}[keyof Api];

export type ReturnTypes<Api extends Record<string, APIClientHandler>> = {
[K in keyof Pick<Api, KeysOfNonVoidResolveValues<Api>>]: TypeJson<ApiClientResponseData<Resolves<Api[K]>>>;
[K in keyof Pick<Api, KeysOfNonVoidResolveValues<Api>>]: TypeJson<ApiServerResponse<Resolves<Api[K]>>>;
};

export type RoutesData<Api extends Record<string, APIServerHandler>> = {[K in keyof Api]: RouteDef};
11 changes: 4 additions & 7 deletions packages/api/test/utils/genericServerTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ import {FetchOpts, HttpClient, IHttpClient} from "../../src/utils/client/index.j
import {ServerRoutes} from "../../src/utils/server/genericJsonServer.js";
import {registerRoute} from "../../src/utils/server/registerRoute.js";
import {HttpStatusCode} from "../../src/utils/client/httpStatusCode.js";
import {APIClientHandler, ApiClientResponseData, ServerApi} from "../../src/interfaces.js";
import {APIClientHandler, ApiServerResponse, ServerApi} from "../../src/interfaces.js";
import {getMockApi, getTestServer} from "./utils.js";

type IgnoreVoid<T> = T extends void ? undefined : T;

export type GenericServerTestCases<Api extends Record<string, APIClientHandler>> = {
export type GenericServerTestCases<Api extends ClientApi> = {
[K in keyof Api]: {
args: Parameters<Api[K]>;
res: IgnoreVoid<ApiClientResponseData<Resolves<Api[K]>>>;
res: IgnoreVoid<ApiServerResponse<Resolves<Api[K]>>>;
query?: FetchOpts["query"];
};
};

export function runGenericServerTest<
Api extends Record<string, APIClientHandler>,
ReqTypes extends {[K in keyof Api]: ReqGeneric},
>(
export function runGenericServerTest<Api extends ClientApi, ReqTypes extends {[K in keyof Api]: ReqGeneric}>(
config: ChainForkConfig,
getClient: (config: ChainForkConfig, https: IHttpClient) => Api,
getRoutes: (config: ChainForkConfig, api: ServerApi<Api>) => ServerRoutes<ServerApi<Api>, ReqTypes>,
Expand Down
6 changes: 3 additions & 3 deletions packages/beacon-node/src/api/impl/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function getNodeApi(
return {data: sync.getSyncStatus()};
},

async getHealth(options, _req, res) {
async getHealth(options) {
// Custom value passed via `syncing_status` query parameter
const syncingStatus = options?.syncingStatus;

Expand All @@ -77,10 +77,10 @@ export function getNodeApi(

if (sync.getSyncStatus().isSyncing) {
// 206: Node is syncing but can serve incomplete data
res?.code(syncingStatus ?? routes.node.NodeHealth.SYNCING);
return {status: syncingStatus ?? routes.node.NodeHealth.SYNCING, response: undefined};
} else {
// 200: Node is ready
res?.code(routes.node.NodeHealth.READY);
return {status: routes.node.NodeHealth.READY, response: undefined};
}
// else {
// 503: Node not initialized or having issues
Expand Down
4 changes: 3 additions & 1 deletion packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ export function getValidatorApi({
randaoReveal,
graffiti
) {
const {data, version, blockValue} = await produceBlockV2(slot, randaoReveal, graffiti);
const produceBlockV2Resp = await produceBlockV2(slot, randaoReveal, graffiti);
const {data, version, blockValue} =
"status" in produceBlockV2Resp ? produceBlockV2Resp.response : produceBlockV2Resp;
if ((data as BlockContents).block !== undefined) {
throw Error(`Invalid block contents for produceBlock at fork=${version}`);
} else {
Expand Down

0 comments on commit e1cbec4

Please sign in to comment.