Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update global exceprion handler #15

Merged
merged 2 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions libs/common/src/exception-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,93 @@ import {
ArgumentsHost,
HttpException,
HttpStatus,
RpcExceptionFilter
RpcExceptionFilter,
Logger
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { RpcException } from '@nestjs/microservices';
import { Observable, throwError } from 'rxjs';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger('CommonService');
constructor(private readonly httpAdapterHost: HttpAdapterHost) { }

// Add explicit types for 'exception' and 'host'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
catch(exception: any, host: ArgumentsHost): void {

const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();

let httpStatus = exception.status; //HttpStatus.INTERNAL_SERVER_ERROR;
let message = '';

let httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
let message = '';
this.logger.error(
`AllExceptionsFilter caught error: ${JSON.stringify(exception)}`
);
switch (exception.constructor) {
case HttpException:

this.logger.error(`Its HttpException`);
httpStatus = (exception as HttpException).getStatus();

message = exception?.response?.error || exception?.message || 'Internal server error';
break;
case RpcException:
httpStatus = exception?.code || exception?.error?.code || HttpStatus.BAD_REQUEST;
message = exception?.response.error;
this.logger.error(`Its RpcException`);
httpStatus = exception?.code || exception?.error?.code || HttpStatus.INTERNAL_SERVER_ERROR;
message = exception?.error || exception?.error?.message?.error || 'RpcException';
break;
default:
httpStatus =
this.logger.error(`Its an Unknown Exception`);
if ('Rpc Exception' === exception.message) {
this.logger.error(`RpcException`);
httpStatus = exception?.error?.code || HttpStatus.INTERNAL_SERVER_ERROR;
message = exception?.error?.message?.error || 'Internal server error';
} else {
httpStatus =
exception.response?.status ||
exception.response?.statusCode ||
exception.code ||
exception.statusCode ||
HttpStatus.INTERNAL_SERVER_ERROR;
message =
exception.response?.data?.message ||
exception.response?.message ||
exception?.message ||
'Internal server error';
}

if (!this.isHttpErrorStatus(httpStatus)) {
this.logger.error(`httpStatus: ${httpStatus}`);
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.logger.error(`Exception Filter : ${message} ${(exception as any)?.stack} ${request.method} ${request.url}`);
const responseBody = {
statusCode: httpStatus,
message,
error: exception.message
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);

const data = httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}

isHttpErrorStatus(statusCode: number): boolean {
return Object.values(HttpStatus).includes(statusCode);
}

}

@Catch(RpcException)
export class CustomExceptionFilter implements RpcExceptionFilter<RpcException> {
private readonly logger = new Logger('CommonService');

// Add explicit types for 'exception' and 'host'
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
this.logger.error(`CustomExceptionFilter caught error: ${JSON.stringify(exception)}`);
return throwError(() => new RpcException({ message: exception.getError(), code: HttpStatus.BAD_REQUEST }));
}
}
}
76 changes: 68 additions & 8 deletions libs/http-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,75 @@
import { Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common';
import { Catch, ExceptionFilter, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { PrismaClientKnownRequestError, PrismaClientValidationError } from '@prisma/client/runtime/library';
import { Observable, throwError } from 'rxjs';

@Catch(HttpException)
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger('CommonService');

catch(exception: HttpException) {
this.logger.log(
`ExceptionFilter caught error: ${JSON.stringify(exception)}`
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
catch(exception: any): Observable<any> {
this.logger.error(`AnyExceptionFilter caught error: ${JSON.stringify(exception)}`);

throw new RpcException(exception);
let httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
let message = '';
switch (exception.constructor) {
case HttpException:
this.logger.error(`Its HttpException`);
httpStatus = exception.getStatus() || HttpStatus.BAD_REQUEST;
message = exception?.getResponse() || exception.message;
break;
case RpcException:
this.logger.error(`Its RpcException`);
return throwError(() => exception.getError());
break;
case PrismaClientKnownRequestError:
this.logger.error(`Its PrismaClientKnownRequestError`);
switch (exception.code) {
case 'P2002': // Unique constraint failed on the {constraint}
case 'P2000': // The provided value for the column is too long for the column's type. Column: {column_name}
case 'P2001': // The record searched for in the where condition ({model_name}.{argument_name} = {argument_value}) does not exist
case 'P2005': // The value {field_value} stored in the database for the field {field_name} is invalid for the field's type
case 'P2006': // The provided value {field_value} for {model_name} field {field_name} is not valid
case 'P2010': // Raw query failed. Code: {code}. Message: {message}
case 'P2011': // Null constraint violation on the {constraint}
case 'P2017': // The records for relation {relation_name} between the {parent_name} and {child_name} models are not connected.
case 'P2021': // The table {table} does not exist in the current database.
case 'P2022': // The column {column} does not exist in the current database.
httpStatus = HttpStatus.BAD_REQUEST;
message = exception?.response?.message || exception?.message;
break;
case 'P2018': // The required connected records were not found. {details}
case 'P2025': // An operation failed because it depends on one or more records that were required but not found. {cause}
case 'P2015': // A related record could not be found. {details}
httpStatus = HttpStatus.NOT_FOUND;
message = exception?.message;
break;
default:
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
message = exception?.response?.message || exception?.message || 'Internal server error';
}
break;
case PrismaClientValidationError:
this.logger.error(`Its PrismaClientValidationError`);
httpStatus = HttpStatus.BAD_REQUEST;
message = exception?.message || exception?.response?.message;
break;
default:
this.logger.error(`Its an Unknown Exception`);
// eslint-disable-next-line no-case-declarations
httpStatus =
exception.response?.status ||
exception.response?.statusCode ||
exception.code ||
HttpStatus.INTERNAL_SERVER_ERROR;
// eslint-disable-next-line no-case-declarations
message =
exception.response?.data?.message ||
exception.response?.message ||
exception?.message ||
'Internal server error';
}
return throwError(() => new RpcException({ message, code: httpStatus }));
}
}
}
45 changes: 5 additions & 40 deletions libs/service/base.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HttpException, Logger } from '@nestjs/common';
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Logger } from '@nestjs/common';

import { ClientProxy } from '@nestjs/microservices';
import { map } from 'rxjs/operators';
Expand All @@ -10,53 +11,17 @@ export class BaseService {
this.logger = new Logger(loggerName);
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
sendNats(serviceProxy: ClientProxy, cmd: string, payload: any): Promise<any> {

const startTs = Date.now();
const pattern = { cmd };

return serviceProxy
.send<string>(pattern, payload)
.pipe(
map((response: string) => ({
response
//duration: Date.now() - startTs,
}))
)
.toPromise()
.catch((error) => {
this.logger.error(`catch: ${JSON.stringify(error)}`);
if (error && error.message) {
throw new HttpException(
{
status: error.statusCode,
error: error.message
},
error.statusCode
);
} else if (error) {
throw new HttpException(
{
status: error.statusCode,
error: error.error
},
error.statusCode

);
} else {
this.logger.error(
`The error received was in an unexpected format. Returning generic 500 error... ${JSON.stringify(
error
)}`
);
throw new HttpException(
{
status: 500,
error: error.message
},
500
);
}
});
.toPromise();
}
}
}