Handle query runner errors (#6424)

- Throw service error from query runner
- Catch in resolver factories 
- Map to graphql errors

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Thomas Trompette
2024-07-27 12:27:04 +02:00
committed by GitHub
parent 3ff24658e1
commit 3060eb4e1e
17 changed files with 281 additions and 297 deletions
@@ -1,4 +1,7 @@
import { BadRequestException } from '@nestjs/common';
import {
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
export const assertIsValidUuid = (value: string) => {
const isValid =
@@ -7,6 +10,9 @@ export const assertIsValidUuid = (value: string) => {
);
if (!isValid) {
throw new BadRequestException(`Value "${value}" is not a valid UUID`);
throw new WorkspaceQueryRunnerException(
`Value "${value}" is not a valid UUID`,
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
);
}
};
@@ -1,8 +1,7 @@
import {
BadRequestException,
HttpException,
InternalServerErrorException,
} from '@nestjs/common';
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
export type PgGraphQLConfig = {
atMost: number;
@@ -13,7 +12,7 @@ interface PgGraphQLErrorMapping {
command: string,
objectName: string,
pgGraphqlConfig: PgGraphQLConfig,
) => HttpException;
) => WorkspaceQueryRunnerException;
}
const pgGraphQLCommandMapping = {
@@ -24,18 +23,28 @@ const pgGraphQLCommandMapping = {
const pgGraphQLErrorMapping: PgGraphQLErrorMapping = {
'delete impacts too many records': (_, objectName, pgGraphqlConfig) =>
new BadRequestException(
new WorkspaceQueryRunnerException(
`Cannot delete ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
),
'update impacts too many records': (_, objectName, pgGraphqlConfig) =>
new BadRequestException(
new WorkspaceQueryRunnerException(
`Cannot update ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
),
'duplicate key value violates unique constraint': (command, objectName, _) =>
new BadRequestException(
new WorkspaceQueryRunnerException(
`Cannot ${
pgGraphQLCommandMapping[command] ?? command
} ${objectName} because it violates a uniqueness constraint.`,
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT,
),
'violates foreign key constraint': (command, objectName, _) =>
new WorkspaceQueryRunnerException(
`Cannot ${
pgGraphQLCommandMapping[command] ?? command
} ${objectName} because it violates a foreign key constraint.`,
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT,
),
};
@@ -49,7 +58,7 @@ export const computePgGraphQLError = (
const errorMessage = error?.message;
const mappedErrorKey = Object.keys(pgGraphQLErrorMapping).find(
(key) => errorMessage?.startsWith(key),
(key) => errorMessage?.includes(key),
);
const mappedError = mappedErrorKey
@@ -60,7 +69,8 @@ export const computePgGraphQLError = (
return mappedError(command, objectName, pgGraphqlConfig);
}
return new InternalServerErrorException(
return new WorkspaceQueryRunnerException(
`GraphQL errors on ${command}${objectName}: ${JSON.stringify(error)}`,
WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR,
);
};
@@ -0,0 +1,36 @@
import {
WorkspaceQueryRunnerException,
WorkspaceQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
import {
ForbiddenError,
InternalServerError,
NotFoundError,
TimeoutError,
UserInputError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
error: Error,
) => {
if (error instanceof WorkspaceQueryRunnerException) {
switch (error.code) {
case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
throw new NotFoundError(error.message);
case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
throw new UserInputError(error.message);
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
throw new ForbiddenError(error.message);
case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
throw new TimeoutError(error.message);
case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
default:
throw new InternalServerError(error.message);
}
}
throw error;
};