RestApiExceptionFilter is used as an exception filter for the core controller which is used for crud operations on our objects (equivalent of our dynamic queries findManyPeople etc. on the graphql API). Exceptions were leading a 400 / BadRequestException response status which can be confusing to users. By default we should actually throw a 500 if the error was not handled priorily, but we have not implemented input validation for the REST api so we fear to be flooded with errors that should not be 500 but 400 due to user inputs. A solution should be brought [with this ticket](https://github.com/twentyhq/core-team-issues/issues/1027) but it has not been prioritized yet.
161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
import { HttpException } from '@nestjs/common';
|
|
|
|
import { GraphQLError } from 'graphql';
|
|
|
|
import { ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-user.interface';
|
|
import { ExceptionHandlerWorkspace } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-workspace.interface';
|
|
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
|
|
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
|
import {
|
|
AuthenticationError,
|
|
BaseGraphQLError,
|
|
ConflictError,
|
|
ErrorCode,
|
|
ForbiddenError,
|
|
MethodNotAllowedError,
|
|
NotFoundError,
|
|
TimeoutError,
|
|
ValidationError,
|
|
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
|
import { CustomException } from 'src/utils/custom-exception';
|
|
|
|
const graphQLPredefinedExceptions = {
|
|
400: ValidationError,
|
|
401: AuthenticationError,
|
|
403: ForbiddenError,
|
|
404: NotFoundError,
|
|
405: MethodNotAllowedError,
|
|
408: TimeoutError,
|
|
409: ConflictError,
|
|
};
|
|
|
|
export const graphQLErrorCodesToFilter = [
|
|
ErrorCode.GRAPHQL_VALIDATION_FAILED,
|
|
ErrorCode.UNAUTHENTICATED,
|
|
ErrorCode.FORBIDDEN,
|
|
ErrorCode.NOT_FOUND,
|
|
ErrorCode.METHOD_NOT_ALLOWED,
|
|
ErrorCode.TIMEOUT,
|
|
ErrorCode.CONFLICT,
|
|
ErrorCode.BAD_USER_INPUT,
|
|
];
|
|
|
|
export const handleExceptionAndConvertToGraphQLError = (
|
|
exception: Error,
|
|
exceptionHandlerService: ExceptionHandlerService,
|
|
user?: ExceptionHandlerUser,
|
|
workspace?: ExceptionHandlerWorkspace,
|
|
): BaseGraphQLError => {
|
|
handleException({
|
|
exception,
|
|
exceptionHandlerService,
|
|
user,
|
|
workspace,
|
|
});
|
|
|
|
return convertExceptionToGraphQLError(exception);
|
|
};
|
|
|
|
export const shouldCaptureException = (
|
|
exception: Error,
|
|
statusCode?: number,
|
|
): boolean => {
|
|
if (
|
|
exception instanceof GraphQLError &&
|
|
(exception?.extensions?.http?.status ?? 500) < 500
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
exception instanceof BaseGraphQLError &&
|
|
graphQLErrorCodesToFilter.includes(exception?.extensions?.code)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (exception instanceof HttpException && exception.getStatus() < 500) {
|
|
return false;
|
|
}
|
|
|
|
if (statusCode && statusCode < 500) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
export const handleException = <
|
|
T extends Error | CustomException | HttpException,
|
|
>({
|
|
exception,
|
|
exceptionHandlerService,
|
|
user,
|
|
workspace,
|
|
statusCode,
|
|
}: {
|
|
exception: T;
|
|
exceptionHandlerService: ExceptionHandlerService;
|
|
user?: ExceptionHandlerUser;
|
|
workspace?: ExceptionHandlerWorkspace;
|
|
statusCode?: number;
|
|
}): T => {
|
|
if (shouldCaptureException(exception, statusCode)) {
|
|
exceptionHandlerService.captureExceptions([exception], { user, workspace });
|
|
}
|
|
|
|
return exception;
|
|
};
|
|
|
|
export const convertExceptionToGraphQLError = (
|
|
exception: Error,
|
|
): BaseGraphQLError => {
|
|
if (exception instanceof HttpException) {
|
|
return convertHttpExceptionToGraphql(exception);
|
|
}
|
|
if (exception instanceof BaseGraphQLError) {
|
|
return exception;
|
|
}
|
|
|
|
return convertExceptionToGraphql(exception);
|
|
};
|
|
|
|
const convertHttpExceptionToGraphql = (exception: HttpException) => {
|
|
const status = exception.getStatus();
|
|
let error: BaseGraphQLError;
|
|
|
|
if (status in graphQLPredefinedExceptions) {
|
|
// @ts-expect-error legacy noImplicitAny
|
|
const message = exception.getResponse()['message'] ?? exception.message;
|
|
|
|
// @ts-expect-error legacy noImplicitAny
|
|
error = new graphQLPredefinedExceptions[exception.getStatus()](message);
|
|
} else {
|
|
error = new BaseGraphQLError(
|
|
'Internal Server Error',
|
|
exception.getStatus().toString(),
|
|
);
|
|
}
|
|
|
|
// Only show the stack trace in development mode
|
|
if (process.env.NODE_ENV === NodeEnvironment.DEVELOPMENT) {
|
|
error.stack = exception.stack;
|
|
error.extensions['response'] = exception.getResponse();
|
|
}
|
|
|
|
return error;
|
|
};
|
|
|
|
export const convertExceptionToGraphql = (exception: Error) => {
|
|
const error = new BaseGraphQLError(
|
|
exception.name,
|
|
ErrorCode.INTERNAL_SERVER_ERROR,
|
|
);
|
|
|
|
error.stack = exception.stack;
|
|
error.extensions['response'] = exception.message;
|
|
|
|
return error;
|
|
};
|