diff --git a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts index 3f181f418..4bdc9860d 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts @@ -10,7 +10,10 @@ import { GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { generateGraphQLErrorFromError } from 'src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util'; -import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { + BaseGraphQLError, + convertGraphQLErrorToBaseGraphQLError, +} from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { shouldCaptureException } from 'src/engine/utils/global-exception-handler.util'; const DEFAULT_EVENT_ID_KEY = 'exceptionEventId'; @@ -81,13 +84,29 @@ export const useGraphQLErrorHandlerHook = < return; } - // Step 1: Flatten errors - extract original errors when available - const originalErrors = result.errors.map((error) => { - return error.originalError || error; + // Step 1: Process errors - extract original errors and convert to BaseGraphQLError + const processedErrors = result.errors.map((error) => { + const originalError = error.originalError || error; + + if (error.extensions && originalError !== error) { + originalError.extensions = { + ...error.extensions, + ...(originalError.extensions || {}), + }; + } + + if ( + originalError instanceof GraphQLError && + !(originalError instanceof BaseGraphQLError) + ) { + return convertGraphQLErrorToBaseGraphQLError(originalError); + } + + return originalError; }); // Step 2: Send errors to monitoring service (with stack traces) - const errorsToCapture = originalErrors.filter( + const errorsToCapture = processedErrors.filter( shouldCaptureException, ); @@ -107,15 +126,15 @@ export const useGraphQLErrorHandlerHook = < errorsToCapture.forEach((_, i) => { if (eventIds?.[i] && eventIdKey !== null) { - originalErrors[ - originalErrors.indexOf(errorsToCapture[i]) + processedErrors[ + processedErrors.indexOf(errorsToCapture[i]) ].eventId = eventIds[i]; } }); } // Step 3: Transform errors for GraphQL response (clean GraphQL errors) - const transformedErrors = originalErrors.map((error) => { + const transformedErrors = processedErrors.map((error) => { const graphqlError = error instanceof BaseGraphQLError ? error diff --git a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts index 72c080ee5..8a4afa07c 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts @@ -196,3 +196,51 @@ export class InternalServerError extends BaseGraphQLError { Object.defineProperty(this, 'name', { value: 'InternalServerError' }); } } + +/** + * Converts a GraphQLError to a BaseGraphQLError with the appropriate ErrorCode + * based on HTTP status code if present in extensions. + */ +export const convertGraphQLErrorToBaseGraphQLError = ( + error: GraphQLError, +): BaseGraphQLError => { + const httpStatus = error.extensions?.http?.status; + let errorCode = ErrorCode.INTERNAL_SERVER_ERROR; + + if (httpStatus && typeof httpStatus === 'number') { + switch (httpStatus) { + case 400: + errorCode = ErrorCode.BAD_USER_INPUT; + break; + case 401: + errorCode = ErrorCode.UNAUTHENTICATED; + break; + case 403: + errorCode = ErrorCode.FORBIDDEN; + break; + case 404: + errorCode = ErrorCode.NOT_FOUND; + break; + case 405: + errorCode = ErrorCode.METHOD_NOT_ALLOWED; + break; + case 408: + case 504: + errorCode = ErrorCode.TIMEOUT; + break; + case 409: + errorCode = ErrorCode.CONFLICT; + break; + default: + if (httpStatus >= 400 && httpStatus < 500) { + // Other 4xx errors + errorCode = ErrorCode.BAD_USER_INPUT; + } else { + // 5xx errors default to internal server error + errorCode = ErrorCode.INTERNAL_SERVER_ERROR; + } + } + } + + return new BaseGraphQLError(error.message, errorCode, error.extensions); +};