Skip to content

Commit

Permalink
Merge pull request #15 from credebl/283-global-exception-handler
Browse files Browse the repository at this point in the history
update global exceprion handler
  • Loading branch information
KulkarniShashank authored Aug 10, 2023
2 parents adeefac + f1f44cf commit 4f057d2
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 59 deletions.
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();
}
}
}

0 comments on commit 4f057d2

Please sign in to comment.