diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 0249da325d8..1c806cda1dd 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -236,7 +236,7 @@ export async function processGraphQLRequest( parsingDidEnd(); } catch (syntaxError) { parsingDidEnd(syntaxError); - return sendErrorResponse(syntaxError, SyntaxError); + return await sendErrorResponse(syntaxError, SyntaxError); } const validationDidEnd = await dispatcher.invokeDidStartHook( @@ -253,7 +253,7 @@ export async function processGraphQLRequest( validationDidEnd(); } else { validationDidEnd(validationErrors); - return sendErrorResponse(validationErrors, ValidationError); + return await sendErrorResponse(validationErrors, ValidationError); } if (config.documentStore) { @@ -309,7 +309,7 @@ export async function processGraphQLRequest( if (err instanceof HttpQueryError) { throw err; } - return sendErrorResponse(err); + return await sendErrorResponse(err); } // Now that we've gone through the pre-execution phases of the request @@ -345,7 +345,7 @@ export async function processGraphQLRequest( >); if (result.errors) { - extensionStack.didEncounterErrors(result.errors); + await didEncounterErrors(result.errors); } response = { @@ -356,7 +356,7 @@ export async function processGraphQLRequest( executionDidEnd(); } catch (executionError) { executionDidEnd(executionError); - return sendErrorResponse(executionError); + return await sendErrorResponse(executionError); } } @@ -486,7 +486,20 @@ export async function processGraphQLRequest( return requestContext.response!; } - function sendErrorResponse( + async function didEncounterErrors(errors: ReadonlyArray) { + requestContext.errors = errors; + extensionStack.didEncounterErrors(errors); + + return await dispatcher.invokeHookAsync( + 'didEncounterErrors', + requestContext as WithRequired< + typeof requestContext, + 'metrics' | 'source' | 'errors' + >, + ); + } + + async function sendErrorResponse( errorOrErrors: ReadonlyArray | GraphQLError, errorClass?: typeof ApolloError, ) { @@ -495,7 +508,7 @@ export async function processGraphQLRequest( ? errorOrErrors : [errorOrErrors]; - extensionStack.didEncounterErrors(errors); + await didEncounterErrors(errors); return sendResponse({ errors: formatErrors( diff --git a/packages/apollo-server-core/src/requestPipelineAPI.ts b/packages/apollo-server-core/src/requestPipelineAPI.ts index ac8a2fd0a3f..06a422d302c 100644 --- a/packages/apollo-server-core/src/requestPipelineAPI.ts +++ b/packages/apollo-server-core/src/requestPipelineAPI.ts @@ -75,6 +75,14 @@ export interface GraphQLRequestContext> { readonly operationName?: string | null; readonly operation?: OperationDefinitionNode; + /** + * Unformatted errors which have occurred during the request. Note that these + * are present earlier in the request pipeline and differ from **formatted** + * errors which are the result of running the user-configurable `formatError` + * transformation function over specific errors. + */ + readonly errors?: ReadonlyArray; + readonly metrics?: GraphQLRequestMetrics; debug?: boolean; diff --git a/packages/apollo-server-plugin-base/src/index.ts b/packages/apollo-server-plugin-base/src/index.ts index f26e262eb8c..ecc7ca5ec0f 100644 --- a/packages/apollo-server-plugin-base/src/index.ts +++ b/packages/apollo-server-plugin-base/src/index.ts @@ -42,6 +42,12 @@ export interface GraphQLRequestListener> { 'metrics' | 'source' | 'document' | 'operationName' | 'operation' >, ): ValueOrPromise; + didEncounterErrors?( + requestContext: WithRequired< + GraphQLRequestContext, + 'metrics' | 'source' | 'errors' + >, + ): ValueOrPromise; // If this hook is defined, it is invoked immediately before GraphQL execution // would take place. If its return value resolves to a non-null // GraphQLResponse, that result is used instead of executing the query.