coffee-core
Version:
Coffee IT API core library
134 lines (116 loc) • 4.49 kB
text/typescript
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';
()
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(' ', '_');
}
}