fastify-http-errors-enhanced
Version:
A error handling plugin for Fastify that uses enhanced HTTP errors.
65 lines (64 loc) • 3.55 kB
JavaScript
import { BadRequestError, INTERNAL_SERVER_ERROR, InternalServerError, NotFoundError, UnprocessableEntityError, UnsupportedMediaTypeError, addAdditionalProperties, messagesByCodes, serializeError } from 'http-errors-enhanced';
import { kHttpErrorsEnhancedConfiguration } from './interfaces.js';
import { upperFirst } from './utils.js';
import { convertValidationErrors, validationMessagesFormatters } from './validation.js';
export function handleNotFoundError(request, reply) {
handleErrors(new NotFoundError('Not found.'), request, reply);
}
export function handleValidationError(error, request) {
/*
As seen in https://github.com/fastify/fastify/blob/master/lib/validation.js
the error.message will always start with the relative section (params, querystring, headers, body)
and fastify throws on first failing section.
*/ const section = error.message.match(/^\w+/)[0];
const Klass = request[kHttpErrorsEnhancedConfiguration]?.use422ForValidationErrors ? UnprocessableEntityError : BadRequestError;
return new Klass('One or more validations failed trying to process your request.', {
failedValidations: convertValidationErrors(section, Reflect.get(request, section), error.validation)
});
}
export function handleErrors(error, request, reply) {
if (request[kHttpErrorsEnhancedConfiguration]?.preHandler) {
error = request[kHttpErrorsEnhancedConfiguration].preHandler(error);
}
// It is a generic error, handle it
const code = error.code;
if (!('statusCode' in error) || 'validation' in error) {
if ('validation' in error && request[kHttpErrorsEnhancedConfiguration]?.convertValidationErrors) {
// If it is a validation error, convert errors to human friendly format
error = handleValidationError(error, request);
} else if (request[kHttpErrorsEnhancedConfiguration]?.hideUnhandledErrors) {
// It is requested to hide the error, just log it and then create a generic one
request.log.error({
error: serializeError(error)
});
error = new InternalServerError('An error occurred trying to process your request.');
} else {
// Wrap in a HttpError, making the stack explicitily available
error = new InternalServerError(serializeError(error));
Object.defineProperty(error, 'stack', {
enumerable: true
});
}
} else if (code === 'INVALID_CONTENT_TYPE' || code === 'FST_ERR_CTP_INVALID_MEDIA_TYPE') {
error = new UnsupportedMediaTypeError(upperFirst(validationMessagesFormatters.contentType()));
} else if (code === 'FST_ERR_CTP_EMPTY_JSON_BODY') {
error = new BadRequestError(upperFirst(validationMessagesFormatters.jsonEmpty()));
} else if (code === 'MALFORMED_JSON' || error.name === 'SyntaxError' || error.message === 'Invalid JSON' || error.stack.includes('at JSON.parse')) {
error = new BadRequestError(upperFirst(validationMessagesFormatters.json()));
}
// Get the status code
let { statusCode, headers } = error;
// Code outside HTTP range
if (statusCode < 100 || statusCode > 599) {
statusCode = INTERNAL_SERVER_ERROR;
}
// Create the body
const body = {
statusCode,
error: messagesByCodes[statusCode],
message: error.message
};
addAdditionalProperties(body, error);
// Send the error back
reply.code(statusCode).headers(headers ?? {}).type('application/json').send(body);
}