UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

91 lines (90 loc) 3.9 kB
import { ErrorCode, InternalServerError, isDirectusError } from '@directus/errors'; import { isObject } from '@directus/utils'; import { getNodeEnv } from '@directus/utils/node'; import getDatabase from '../database/index.js'; import emitter from '../emitter.js'; import { useLogger } from '../logger/index.js'; const FALLBACK_ERROR = new InternalServerError(); export const errorHandler = asyncErrorHandler(async (err, req, res) => { const logger = useLogger(); let errors = []; let status = null; // It can be assumed that at least one error is given const receivedErrors = Array.isArray(err) ? err : [err]; for (const error of receivedErrors) { // In dev mode, if available, expose stack trace under error's extensions data if (getNodeEnv() === 'development' && error instanceof Error && error.stack) { (error.extensions ??= {})['stack'] = error.stack; } if (isDirectusError(error)) { logger.debug(error); if (status === null) { // Use current error status as response status status = error.status; } else if (status !== error.status) { // Fallback if status has already been set by a preceding error // and doesn't match the current one status = FALLBACK_ERROR.status; } errors.push({ message: error.message, extensions: { ...(error.extensions ?? {}), // Expose error code under error's extensions data code: error.code, }, }); if (isDirectusError(error, ErrorCode.MethodNotAllowed)) { res.header('Allow', error.extensions.allowed.join(', ')); } } else { logger.error(error); status = FALLBACK_ERROR.status; if (req.accountability?.admin === true) { const localError = isObject(error) ? error : {}; // Use 'message' prop if available, otherwise if 'error' is a string use that const message = (typeof localError['message'] === 'string' ? localError['message'] : null) ?? (typeof error === 'string' ? error : null); errors = [ { message: message || FALLBACK_ERROR.message, extensions: { code: FALLBACK_ERROR.code, ...(localError['extensions'] ?? {}), }, }, ]; } else { // Don't expose unknown errors to non-admin users errors = [{ message: FALLBACK_ERROR.message, extensions: { code: FALLBACK_ERROR.code } }]; } } } res.status(status ?? FALLBACK_ERROR.status); const updatedErrors = await emitter.emitFilter('request.error', errors, {}, { database: getDatabase(), schema: req.schema, accountability: req.accountability ?? null, }); return res.json({ errors: updatedErrors }); }); function asyncErrorHandler(fn) { return (err, req, res, next) => fn(err, req, res, next).catch((error) => { // To be on the safe side and ensure this doesn't lead to an unhandled (potentially crashing) error try { const logger = useLogger(); logger.error(error, 'Unexpected error in error handler'); } catch { // Ignore } // Delegate to default error handler to close the connection if (res.headersSent) return next(err); res.status(FALLBACK_ERROR.status); return res.json({ errors: [{ message: FALLBACK_ERROR.message, extensions: { code: FALLBACK_ERROR.code } }] }); }); }