Files
twenty/packages/twenty-server/src/engine/integrations/exception-handler/hooks/use-exception-handler.hook.ts
T
Weiko 1dff5bf957 Fix custom errors thrown as 500 (#6238)
We call convertExceptionToGraphQLError in the exception handler for http
exceptions but we don't take into account those that already are
graphqlErrors and because of that the logic of convertExceptionToGraphql
is to fallback to a 500.
Now if the exception is a BaseGraphqlError (custom graphql error we
throw in the code), we throw them directly.

BEFORE
<img width="957" alt="Screenshot 2024-07-12 at 15 33 03"
src="https://github.com/user-attachments/assets/22ddae13-4996-4ad3-8f86-dd17c2922ca8">


AFTER
<img width="923" alt="Screenshot 2024-07-12 at 15 32 01"
src="https://github.com/user-attachments/assets/d3d6db93-6d28-495c-a4b4-ba4e47d45abd">

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2024-07-12 17:56:21 +02:00

151 lines
4.6 KiB
TypeScript

import {
OnExecuteDoneHookResultOnNextHook,
Plugin,
getDocumentString,
handleStreamOrSingleExecutionResult,
} from '@envelop/core';
import { GraphQLError, Kind, OperationDefinitionNode, print } from 'graphql';
import { GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces/graphql-context.interface';
import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service';
import {
convertExceptionToGraphQLError,
shouldFilterException,
} from 'src/engine/utils/global-exception-handler.util';
export type ExceptionHandlerPluginOptions = {
/**
* The exception handler service to use.
*/
exceptionHandlerService: ExceptionHandlerService;
/**
* The key of the event id in the error's extension. `null` to disable.
* @default exceptionEventId
*/
eventIdKey?: string | null;
};
// This hook is deprecated.
// We should either handle exception in the context of graphql, controller or command
// @deprecated
export const useExceptionHandler = <PluginContext extends GraphQLContext>(
options: ExceptionHandlerPluginOptions,
): Plugin<PluginContext> => {
const eventIdKey = options.eventIdKey === null ? null : 'exceptionEventId';
function addEventId(
err: GraphQLError,
eventId: string | undefined | null,
): GraphQLError {
if (eventIdKey !== null && eventId) {
err.extensions[eventIdKey] = eventId;
}
return err;
}
return {
async onExecute({ args }) {
const exceptionHandlerService = options.exceptionHandlerService;
const rootOperation = args.document.definitions.find(
(o) => o.kind === Kind.OPERATION_DEFINITION,
) as OperationDefinitionNode;
const operationType = rootOperation.operation;
const user = args.contextValue.req.user;
const document = getDocumentString(args.document, print);
const opName =
args.operationName ||
rootOperation.name?.value ||
'Anonymous Operation';
return {
onExecuteDone(payload) {
const handleResult: OnExecuteDoneHookResultOnNextHook<object> = ({
result,
setResult,
}) => {
if (result.errors && result.errors.length > 0) {
const exceptions = result.errors.reduce<{
filtered: any[];
unfiltered: any[];
}>(
(acc, err) => {
// Filter out exceptions that we don't want to be captured by exception handler
if (shouldFilterException(err?.originalError ?? err)) {
acc.filtered.push(err);
} else {
acc.unfiltered.push(err);
}
return acc;
},
{
filtered: [],
unfiltered: [],
},
);
if (exceptions.unfiltered.length > 0) {
const eventIds = exceptionHandlerService.captureExceptions(
exceptions.unfiltered,
{
operation: {
name: opName,
type: operationType,
},
document,
user,
},
);
exceptions.unfiltered.map((err, i) =>
addEventId(err, eventIds?.[i]),
);
}
const concatenatedErrors = [
...exceptions.filtered,
...exceptions.unfiltered,
];
const errors = concatenatedErrors.map((err) => {
if (!err.originalError) {
return err;
}
return convertExceptionToGraphQLError(err.originalError);
});
setResult({
...result,
errors,
});
}
};
return handleStreamOrSingleExecutionResult(payload, handleResult);
},
};
},
onValidate: ({ context, validateFn, params: { documentAST, schema } }) => {
const errors = validateFn(schema, documentAST);
if (Array.isArray(errors) && errors.length > 0) {
const headers = context.req.headers;
const currentSchemaVersion = context.req.cacheVersion;
const requestSchemaVersion = headers['x-schema-version'];
if (
requestSchemaVersion &&
requestSchemaVersion !== currentSchemaVersion
) {
throw new GraphQLError(
`Schema version mismatch, please refresh the page.`,
);
}
}
},
};
};