UNPKG

@lokalise/fastify-extras

Version:

Opinionated set of fastify plugins, commonly used in Lokalise

121 lines 4.41 kB
import { isError, isInternalError, isObject, isPublicNonRecoverableError, isStandardizedError, } from '@lokalise/node-core'; import { hasZodFastifySchemaValidationErrors, isResponseSerializationError, } from 'fastify-type-provider-zod'; import pino from 'pino'; const knownAuthErrors = new Set([ 'FST_JWT_NO_AUTHORIZATION_IN_HEADER', 'FST_JWT_AUTHORIZATION_TOKEN_EXPIRED', 'FST_JWT_AUTHORIZATION_TOKEN_INVALID', ]); export function isZodError(value) { return value.name === 'ZodError'; } function resolveLogObject(error) { if (isInternalError(error)) { return { msg: error.message, code: error.errorCode, details: error.details ? JSON.stringify(error.details) : undefined, error: pino.stdSerializers.err({ name: error.name, message: error.message, stack: error.stack, }), }; } return { message: isObject(error) ? error.message : JSON.stringify(error), error: isError(error) ? pino.stdSerializers.err(error) : error, }; } function resolveResponseObject(error) { if (isPublicNonRecoverableError(error)) { return { statusCode: error.httpStatusCode ?? 500, payload: { message: error.message, errorCode: error.errorCode, details: error.details, }, }; } if (hasZodFastifySchemaValidationErrors(error)) { return { statusCode: 400, payload: { message: 'Invalid params', errorCode: 'VALIDATION_ERROR', details: { error: error.validation, }, }, }; } if (isResponseSerializationError(error)) { return { statusCode: 500, payload: { message: 'Invalid response', errorCode: 'RESPONSE_VALIDATION_ERROR', details: { error: error.cause.issues, method: error.method, url: error.url, }, }, }; } if (isStandardizedError(error)) { if (knownAuthErrors.has(error.code)) { const message = error.code === 'FST_JWT_AUTHORIZATION_TOKEN_INVALID' ? 'Authorization token is invalid' : error.message; return { statusCode: 401, payload: { message, errorCode: 'AUTH_FAILED', }, }; } } return { statusCode: 500, payload: { message: 'Internal server error', errorCode: 'INTERNAL_SERVER_ERROR', }, }; } export function createErrorHandler(params) { return function errorHandler(error, request, reply) { const logObject = params.resolveLogObject?.(error) ?? resolveLogObject(error); const responseObject = params.resolveResponseObject?.(error) ?? resolveResponseObject(error); if (responseObject.statusCode >= 500) { params.errorReporter.report({ error: isError(error) ? error : new Error('Unhandled error'), context: { request: { url: request.url, params: request.params, x: request.method, routerPath: request.routeOptions.url, }, 'x-request-id': request.id, // If error is not an instance of Error, include its properties in the context for additional information. // Error details are included in the 'error' property above, so duplicating them in the context is unnecessary. ...(!isError(error) ? error : {}), }, }); // Potentially request can break before we resolved the context if (request.reqContext) { // this preserves correct request id field request.reqContext.logger.error(logObject); } else { request.log.error(logObject); } } void reply.status(responseObject.statusCode).send(responseObject.payload); }; } //# sourceMappingURL=errorHandler.js.map