UNPKG

coffee-core

Version:

Coffee IT API core library

134 lines (116 loc) 4.49 kB
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'; import { Observable, throwError } from 'rxjs'; import { CustomLogger } from '../logger/custom-logger'; import { StatusException } from './status-exception'; import { CoffeeError } from '../domain/error.interface'; const UNKNOWN_ERROR_MESSAGE = 'Internal server error'; const UNKNOWN_ERROR_TYPE = 'unknown_error_type'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { private readonly logger = new CustomLogger(AllExceptionsFilter.name); private static statusCodeRegex = /^[1-5][0-9][0-9]$/; private static getStatusFromMessage(message: string): number { const status = message.replace(/(^\d+)(.+$)/i, '$1'); const defaultStatus = HttpStatus.INTERNAL_SERVER_ERROR; if (!this.isStatusCode(status)) { return defaultStatus; } return parseInt(status, 10) || defaultStatus; } private static isStatusCode(status: string): boolean { return this.statusCodeRegex.test(status); } private static removeStatusFromMessage( statusCode: number, message: string ): string { const statusCodeMessage = statusCode.toString(); const index = message.indexOf(statusCodeMessage); if (index < 0) { return message; } return message.substring(index + statusCodeMessage.length).trim(); } public catch(exception: any, host: ArgumentsHost): void | Observable<any> { if (typeof exception === 'object') { this.logger.error( `Caught exception ${JSON.stringify(exception, null, 2)}` ); } else { this.logger.error(`Caught exception ${exception}`); } const ctx = host.switchToHttp(); const type = host.getType(); const response = ctx.getResponse(); const request = ctx.getRequest(); let requestInfo; const date = new Date(); if (type === 'http') { requestInfo = `Type: HTTPS, IP: ${request.ip}. User Agent: ${request.headers['user-agent']}`; } else if (type === 'rpc') { requestInfo = 'Type: RPC'; } console.log(date.toISOString(), ' - UnhandledError caught in coffee-core:', exception, 'Request info:', requestInfo); if (exception instanceof HttpException) { const exceptionStatusCode = exception.getStatus(); let resultMessage = exception.message; // keep string array notation to overcome 'response' being a private field let errorType = exception['response'].error || UNKNOWN_ERROR_TYPE; const objectResponse = exception.getResponse() as Record<string, unknown>; const objectResponseMessage = objectResponse?.message; if (objectResponseMessage) { if (Array.isArray(objectResponseMessage)) { resultMessage = objectResponseMessage[0]; } else { resultMessage = objectResponseMessage as string; } } return this.handleExceptionResponse(response, { code: exceptionStatusCode, message: resultMessage || '', type: errorType }); } if (exception instanceof StatusException) { return this.handleExceptionResponse(response, { code: exception.status || HttpStatus.INTERNAL_SERVER_ERROR, message: exception.message || UNKNOWN_ERROR_MESSAGE, type: exception.type || UNKNOWN_ERROR_TYPE }); } return this.handleExceptionResponse(response, this.handleUnknownException(exception)); } public handleUnknownException(exception: any): CoffeeError { const message = typeof exception === 'string' ? exception : (exception as any).message; if (message) { const statusCode = AllExceptionsFilter.getStatusFromMessage(message) || HttpStatus.INTERNAL_SERVER_ERROR; const newMessage = AllExceptionsFilter.removeStatusFromMessage(statusCode, message); return { code: statusCode, message: newMessage, type: UNKNOWN_ERROR_TYPE }; } return { code: HttpStatus.INTERNAL_SERVER_ERROR, message: UNKNOWN_ERROR_MESSAGE, type: UNKNOWN_ERROR_TYPE }; } private handleExceptionResponse(response: any, error: CoffeeError): void | Observable<any> { if (!response.status) { return throwError(() => `${error.code} ${error.message}`); } response.status(error.code).json({ ...error, type: this.formatErrorType(error.type) }); } private formatErrorType(type: string): string { return type.trim().toLowerCase().replaceAll(' ', '_'); } }