* Refactor backend folder structure Co-authored-by: Charles Bochet <charles@twenty.com> * fix tests * fix * move yoga hooks --------- Co-authored-by: Charles Bochet <charles@twenty.com>
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { GraphQLError, Kind, OperationDefinitionNode, print } from 'graphql';
|
|
import {
|
|
getDocumentString,
|
|
handleStreamOrSingleExecutionResult,
|
|
OnExecuteDoneHookResultOnNextHook,
|
|
Plugin,
|
|
} from '@envelop/core';
|
|
|
|
import { GraphQLContext } from 'src/engine-graphql-config/interfaces/graphql-context.interface';
|
|
|
|
import { ExceptionHandlerService } from 'src/engine/integrations/exception-handler/exception-handler.service';
|
|
import {
|
|
convertExceptionToGraphQLError,
|
|
filterException,
|
|
} from 'src/engine/filters/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;
|
|
};
|
|
|
|
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.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 (filterException(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) => {
|
|
// Properly convert errors to GraphQLErrors
|
|
const graphQLError = convertExceptionToGraphQLError(
|
|
err.originalError,
|
|
);
|
|
|
|
return graphQLError;
|
|
});
|
|
|
|
setResult({
|
|
...result,
|
|
errors,
|
|
});
|
|
}
|
|
};
|
|
|
|
return handleStreamOrSingleExecutionResult(payload, handleResult);
|
|
},
|
|
};
|
|
},
|
|
};
|
|
};
|