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:
@ -1,150 +0,0 @@
|
||||
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.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user