CustomError та HttpErrorHandler
CustomError
Ditsmod надає два вбудовані класи - CustomError
та HttpErrorHandler
- які можна використовувати, відповідно, для кидання та ловіння помилок.
Клас CustomError
можна компонувати для створення будь-якої помилки:
import { CustomError, Status } from '@ditsmod/core';
// ...
if (someCondition) {
const msg1 = 'message for client';
const msg2 = 'message for logger';
throw new CustomError({ msg1, msg2, level: 'debug', status: Status.BAD_REQUEST });
}
Тобто, в аргументах CustomError
передбачена можливість передачі повідомлень двох типів:
msg1
- повідомлення для передачі клієнту, який створив поточний HTTP-запит;msg2
- повідомлення для логера.
Загалом, конструктор класу CustomError
першим аргументом приймає об'єкт, що має наступний інтерфейс:
interface ErrorInfo {
id?: string | number;
/**
* Message to send it to a client.
*/
msg1?: string = 'Internal server error';
/**
* A message to send it to a logger.
*/
msg2?: string = '';
/**
* Arguments for error handler to send it to a client.
*/
args1?: any;
/**
* Arguments for error handler to send it to a logger.
*/
args2?: any;
/**
* Log level. By default - `warn`.
*/
level?: InputLogLevel = 'warn';
/**
* HTTP status.
*/
status?: Status = Status.BAD_REQUEST;
/**
* The parameters that came with the HTTP request.
*/
params?: any;
}
Конструктор класу CustomError
другим аргументом може приймати cause error, якщо така є.
HttpErrorHandler
Усі помилки, які виникають під час обробки HTTP-запиту, і які ви не зловили у контролерах, інтерсепторах, або сервісах, потрапляють до DefaultHttpErrorHandler. Цей обробник передається до реєстру DI на рівні роуту.
Ви можете створити свій власний обробник помилок, для цього вам потрібно створити клас, що впроваджує інтерфейс HttpErrorHandler:
import { HttpErrorHandler, injectable, isCustomError, Logger, RequestContext, Status } from '@ditsmod/core';
import { randomUUID } from 'node:crypto';
@injectable()
export class MyHttpErrorHandler implements HttpErrorHandler {
constructor(protected logger: Logger) {}
async handleError(err: Error, ctx: RequestContext) {
const requestId = randomUUID();
const errObj = { requestId, err, note: 'This is my implementation of HttpErrorHandler' };
if (isCustomError(err)) {
const { level, status } = err.info;
this.logger.log(level || 'debug', errObj);
this.sendError(err.message, ctx, requestId, status);
} else {
this.logger.log('error', errObj);
const msg = err.message || 'Internal server error';
const status = (err as any).status || Status.INTERNAL_SERVER_ERROR;
this.sendError(msg, ctx, requestId, status);
}
}
protected sendError(error: string, ctx: RequestContext, requestId: string, status?: Status) {
if (!ctx.rawRes.headersSent) {
this.addRequestIdToHeader(requestId, ctx);
ctx.sendJson({ error }, status);
}
}
protected addRequestIdToHeader(requestId: string, ctx: RequestContext) {
ctx.rawRes.setHeader('x-requestId', requestId);
}
}
Щоб централізовано додати ваш новий обробник помилок, можете це зробити прямо у кореневому модулі:
import { rootModule, HttpErrorHandler } from '@ditsmod/core';
import { MyHttpErrorHandler } from './my-http-error-handler.js';
@rootModule({
// ...
providersPerRou: [{ token: HttpErrorHandler, useClass: MyHttpErrorHandler }],
exports: [HttpErrorHandler],
})
export class AppModule {}
Звичайно ж, якщо є специфіка обробки помилок для окремо-взятого модуля, чи контролера, ви точно так само можете додати ваш новий обробник тільки у їхні метадані, без впливу на інші компоненти вашого застосунку.
Якщо ви додаєте такий обробник у метадані некореневого модуля, то навряд чи вам треба його експортувати. З іншого боку, якщо ви захочете написати спеціальний модуль для обробки помилок і захочете все-таки експортувати з нього HttpErrorHandler
, то майте на увазі, що імпорт його у будь-який модуль вимагатиме вирішення колізії провайдерів. Ця особливість виникає через те, що дефолтни й обробник помилок вже додано у кожен модуль вашого застосунку, і при імпортуванні модуля, зі своїм новим обробником помилок, виникає колізія двох обробників помилок. Її можна вирішити досить просто:
import { featureModule, HttpErrorHandler } from '@ditsmod/core';
import { ErrorHandlerModule } from './error-handler.module.js';
@featureModule({
// ...
import: [ErrorHandlerModule]
resolvedCollisionsPerRou: [
[HttpErrorHandler, ErrorHandlerModule],
],
})
export class SomeModule {}
Як бачите, колізія вирішується в масиві resolvedCollisionsPerRou
, оскільки вона відбувається на рівні роуту. Туди передаєте масив з двох елементів, де на першому місці йде токен, з яким відбулась колізія, а на другому місці - модуль, з якого ви хочете експортувати даний провайдер.
Нагадаємо, що колізії провайдерів можуть виникати виключно під час імпорту модулів. Тобто якщо ви локально у межах конкретного модуля створите власний обробник помилок, то колізій не буде.