Skip to content

Commit 63d568d

Browse files
authored
Officially support throwing from didResolveOperation (#7001)
(Also make an existing vaguely related test a bit more clear.)
1 parent e3bd95a commit 63d568d

File tree

5 files changed

+81
-2
lines changed

5 files changed

+81
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/server-integration-testsuite': patch
3+
---
4+
5+
Test the behavior of didResolveOperation hooks throwing.

docs/source/integrations/plugins-event-reference.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ This event is _not_ associated with your GraphQL server's _resolvers_. When this
354354

355355
> If the operation is anonymous (i.e., the operation is `query { ... }` instead of `query NamedQuery { ... }`), then `operationName` is `null`.
356356
357+
If a `didResolveOperation` hook throws an [`GraphQLError`](../data/errors#custom-errors), the error will be serialized and returned to the client, with an HTTP status code of 500 unless [the error specifies a different status code](../data/errors#setting-http-status-code-and-headers). Because this hook has access to the parsed and validated operation as well as request-specific context (such as the `contextValue`) that makes this plugin hook a good place to perform extra validation. (`didResolveOperation` hooks from multiple plugins are run in parallel and if more than one throws, only one error will be sent to the client.)
358+
357359
```ts
358360
didResolveOperation?(
359361
requestContext: WithRequired<

packages/integration-testsuite/src/apolloServerTests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,8 +1099,8 @@ export function defineIntegrationTestSuiteApolloServerTests(
10991099
const reports = await reportIngress.promiseOfReports;
11001100
expect(reports.length).toBe(1);
11011101

1102-
expect(Object.keys(reports[0].tracesPerQuery)[0]).not.toEqual(
1103-
'# -\n{ aliasedField: justAField }',
1102+
expect(Object.keys(reports[0].tracesPerQuery)[0]).toEqual(
1103+
'# -\n{justAField}',
11041104
);
11051105
});
11061106

packages/integration-testsuite/src/httpServerTests.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,74 @@ export function defineIntegrationTestSuiteHttpServerTests(
462462
`);
463463
});
464464

465+
it('throwing in didResolveOperation results in error with default HTTP status code', async () => {
466+
const app = await createApp({
467+
schema,
468+
plugins: [
469+
{
470+
async requestDidStart() {
471+
return {
472+
async didResolveOperation() {
473+
throw new GraphQLError('error with default status');
474+
},
475+
};
476+
},
477+
},
478+
],
479+
});
480+
const res = await request(app)
481+
.post('/')
482+
.send({ query: `{ testString }` });
483+
expect(res.status).toEqual(500);
484+
expect(res.body).toMatchInlineSnapshot(`
485+
{
486+
"errors": [
487+
{
488+
"extensions": {
489+
"code": "INTERNAL_SERVER_ERROR",
490+
},
491+
"message": "error with default status",
492+
},
493+
],
494+
}
495+
`);
496+
});
497+
498+
it('throwing in didResolveOperation results in error with specified HTTP status code', async () => {
499+
const app = await createApp({
500+
schema,
501+
plugins: [
502+
{
503+
async requestDidStart() {
504+
return {
505+
async didResolveOperation() {
506+
throw new GraphQLError('error with another status', {
507+
extensions: { http: { status: 401 }, code: 'OH_NO' },
508+
});
509+
},
510+
};
511+
},
512+
},
513+
],
514+
});
515+
const res = await request(app)
516+
.post('/')
517+
.send({ query: `{ testString }` });
518+
expect(res.status).toEqual(401);
519+
expect(res.body).toMatchInlineSnapshot(`
520+
{
521+
"errors": [
522+
{
523+
"extensions": {
524+
"code": "OH_NO",
525+
},
526+
"message": "error with another status",
527+
},
528+
],
529+
}
530+
`);
531+
});
532+
465533
it('multiple operations with no `operationName` specified returns 400 and OPERATION_RESOLUTION_FAILURE', async () => {
466534
const app = await createApp();
467535
const res = await request(app)

packages/server/src/requestPipeline.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ export async function processGraphQLRequest<TContext extends BaseContext>(
324324
),
325325
);
326326
} catch (err: unknown) {
327+
// Note that we explicitly document throwing `GraphQLError`s from
328+
// `didResolveOperation` as a good way to do validation that depends on the
329+
// validated operation and the request context. (It will have status 500 by
330+
// default.)
327331
return await sendErrorResponse([ensureGraphQLError(err)]);
328332
}
329333

0 commit comments

Comments
 (0)