@lodestar/api
Version:
A Typescript REST client for the Ethereum Consensus API
115 lines • 5.52 kB
JavaScript
import { HttpHeader, MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader, parseContentTypeHeader } from "../headers.js";
import { isRequestWithoutBody, } from "../types.js";
import { WireFormat, fromWireFormat, getWireFormat } from "../wireFormat.js";
import { ApiError } from "./error.js";
export function createFastifyHandler(definition, method, _operationId) {
return async (req, resp) => {
// Determine response wire format first to inform application method
// about the preferable return type to avoid unnecessary serialization
let responseMediaType;
const acceptHeader = req.headers.accept;
if (definition.resp.isEmpty) {
// Ignore Accept header, the response will be sent without body
responseMediaType = null;
}
else if (acceptHeader === undefined) {
// Default to json to not force user to set header, e.g. when using curl
responseMediaType = MediaType.json;
}
else {
const { onlySupport } = definition.resp;
const supportedMediaTypes = onlySupport !== undefined ? [fromWireFormat(onlySupport)] : SUPPORTED_MEDIA_TYPES;
responseMediaType = parseAcceptHeader(acceptHeader, supportedMediaTypes);
if (responseMediaType === null) {
throw new ApiError(406, `Accepted media types not supported: ${acceptHeader}`);
}
}
const responseWireFormat = responseMediaType !== null ? getWireFormat(responseMediaType) : null;
let requestWireFormat;
if (isRequestWithoutBody(definition)) {
requestWireFormat = null;
}
else {
const contentType = req.headers[HttpHeader.ContentType];
if (contentType === undefined && req.body === undefined) {
// Default to json parser if body is omitted. This is not possible for most
// routes as request will fail schema validation before this handler is called
requestWireFormat = WireFormat.json;
}
else {
if (contentType === undefined) {
throw new ApiError(400, "Content-Type header is required");
}
const requestMediaType = parseContentTypeHeader(contentType);
if (requestMediaType === null) {
throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`);
}
requestWireFormat = getWireFormat(requestMediaType);
}
const { onlySupport } = definition.req;
if (onlySupport !== undefined && onlySupport !== requestWireFormat) {
throw new ApiError(415, `Endpoint only supports ${onlySupport.toUpperCase()} requests`);
}
}
let args;
try {
switch (requestWireFormat) {
case WireFormat.json:
args = definition.req.parseReqJson(req);
break;
case WireFormat.ssz:
args = definition.req.parseReqSsz(req);
break;
case null:
args = definition.req.parseReq(req);
break;
}
}
catch (e) {
if (e instanceof ApiError)
throw e;
// Errors related to parsing should return 400 status code
throw new ApiError(400, e.message);
}
const response = await method(args, {
sszBytes: requestWireFormat === WireFormat.ssz ? req.body : null,
returnBytes: responseWireFormat === WireFormat.ssz,
});
if (response?.status !== undefined) {
resp.statusCode = response.status;
}
switch (responseWireFormat) {
case WireFormat.json: {
const metaHeaders = definition.resp.meta.toHeadersObject(response?.meta);
metaHeaders[HttpHeader.ContentType] = MediaType.json;
void resp.headers(metaHeaders);
const data = response?.data instanceof Uint8Array
? definition.resp.data.toJson(definition.resp.data.deserialize(response.data, response.meta), response.meta)
: definition.resp.data.toJson(response?.data, response?.meta);
const metaJson = definition.resp.meta.toJson(response?.meta);
if (definition.resp.transform) {
return definition.resp.transform.toResponse(data, metaJson);
}
return {
data,
...metaJson,
};
}
case WireFormat.ssz: {
const metaHeaders = definition.resp.meta.toHeadersObject(response?.meta);
metaHeaders[HttpHeader.ContentType] = MediaType.ssz;
void resp.headers(metaHeaders);
const data = response?.data instanceof Uint8Array
? response.data
: definition.resp.data.serialize(response?.data, response?.meta);
// Fastify supports returning `Uint8Array` from handler and will efficiently
// convert it to a `Buffer` internally without copying the underlying `ArrayBuffer`
return data;
}
case null:
// Send response without body
return;
}
};
}
//# sourceMappingURL=handler.js.map