Skip to content

Commit

Permalink
Add didEncounterErrors to request pipeline API.
Browse files Browse the repository at this point in the history
(As I mentioned at the bottom of
#2714)

This adds a new life-cycle event to the new request pipeline API called
`didEncounterErrors`, which receives `requestContext`'s **unformatted**
`errors`.  (The `requestContext` represents an individual request within
Apollo Server.)  These `errors` give access to potentially different
`errors` than those received within `response.errors`, which are sent to the
client and available on the `willSendResponse` life-cycle hook, since they
are **not** passed through the `formatError` transformation.  This can allow
plugin implementers to take advantage of the actual errors and properties
which may be pruned from the error before transmission to the client.

While most other request pipeline life-cycle events provide a guarantee of
their arguments, this `didEncounterErrors` will have a little bit less
certainty since the errors could have happened in parsing, validation, or
execution, and thus different contracts would have been made (e.g. we may
not have been able to negotiate a `requestContext.document` if we could not
parse the operation).

This still needs tests and I suspect I'll have some additional changes
prior to this becoming official, but it can ship as-is for now and will live
in the Apollo Server 2.6.0 alpha for the time-being.  Use with minimal risk,
but with the caveat that the final API may change.

Also, I'll need to add docs to
#2008.
  • Loading branch information
abernix committed May 24, 2019
1 parent 06ff964 commit c7320e0
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 7 deletions.
27 changes: 20 additions & 7 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export async function processGraphQLRequest<TContext>(
parsingDidEnd();
} catch (syntaxError) {
parsingDidEnd(syntaxError);
return sendErrorResponse(syntaxError, SyntaxError);
return await sendErrorResponse(syntaxError, SyntaxError);
}

const validationDidEnd = await dispatcher.invokeDidStartHook(
Expand All @@ -253,7 +253,7 @@ export async function processGraphQLRequest<TContext>(
validationDidEnd();
} else {
validationDidEnd(validationErrors);
return sendErrorResponse(validationErrors, ValidationError);
return await sendErrorResponse(validationErrors, ValidationError);
}

if (config.documentStore) {
Expand Down Expand Up @@ -309,7 +309,7 @@ export async function processGraphQLRequest<TContext>(
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
Expand Down Expand Up @@ -345,7 +345,7 @@ export async function processGraphQLRequest<TContext>(
>);

if (result.errors) {
extensionStack.didEncounterErrors(result.errors);
await didEncounterErrors(result.errors);
}

response = {
Expand All @@ -356,7 +356,7 @@ export async function processGraphQLRequest<TContext>(
executionDidEnd();
} catch (executionError) {
executionDidEnd(executionError);
return sendErrorResponse(executionError);
return await sendErrorResponse(executionError);
}
}

Expand Down Expand Up @@ -486,7 +486,20 @@ export async function processGraphQLRequest<TContext>(
return requestContext.response!;
}

function sendErrorResponse(
async function didEncounterErrors(errors: ReadonlyArray<GraphQLError>) {
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> | GraphQLError,
errorClass?: typeof ApolloError,
) {
Expand All @@ -495,7 +508,7 @@ export async function processGraphQLRequest<TContext>(
? errorOrErrors
: [errorOrErrors];

extensionStack.didEncounterErrors(errors);
await didEncounterErrors(errors);

return sendResponse({
errors: formatErrors(
Expand Down
8 changes: 8 additions & 0 deletions packages/apollo-server-core/src/requestPipelineAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ export interface GraphQLRequestContext<TContext = Record<string, any>> {
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<GraphQLError>;

readonly metrics?: GraphQLRequestMetrics;

debug?: boolean;
Expand Down
6 changes: 6 additions & 0 deletions packages/apollo-server-plugin-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export interface GraphQLRequestListener<TContext = Record<string, any>> {
'metrics' | 'source' | 'document' | 'operationName' | 'operation'
>,
): ValueOrPromise<void>;
didEncounterErrors?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>,
'metrics' | 'source' | 'errors'
>,
): ValueOrPromise<void>;
// 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.
Expand Down

0 comments on commit c7320e0

Please sign in to comment.