Skip to content

Commit

Permalink
Merge branch 'abernix/add-willResolveField-and-didResolveField' into …
Browse files Browse the repository at this point in the history
…abernix/migrate-cache-control-to-plugin-api
  • Loading branch information
abernix committed May 8, 2020
2 parents 19ecb95 + 1121785 commit 4306673
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 43 deletions.
26 changes: 22 additions & 4 deletions docs/source/integrations/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,18 @@ The following diagram illustrates the sequence of events that fire for each requ

```mermaid
graph TB;
request(requestDidStart) --> parsing(parsingDidStart*);
request(requestDidStart) --> resolveSource(didResolveSource);
resolveSource --"Success"--> parsing(parsingDidStart*);
parsing --"Success"--> validation(validationDidStart*);
validation --"Success"--> resolve(didResolveOperation);
resolve --"Success"--> response(responseForOperation);
validation --"Success"--> resolveOperation(didResolveOperation);
resolveOperation --"Success"--> response(responseForOperation);
execution(executionDidStart*);
errors(didEncounterErrors);
response --"Response provided"--> send;
response --"No response provided"--> execution;
execution --"Success"--> send(willSendResponse);
execution & resolve & parsing & validation --"Failure"--> errors;
execution & resolveSource & resolveOperation & parsing & validation --"Failure"--> errors;
errors --> send;
class server,request secondary;
```
Expand Down Expand Up @@ -313,6 +314,23 @@ should not return a value.

> If you're using TypeScript to create your plugin, implement the [ `GraphQLRequestListener` interface](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-plugin-base/src/index.ts) from the `apollo-server-plugin-base` module to define functions for request lifecycle events.
### `didResolveSource`

The `didResolveSource` event is invoked after Apollo Server has determined the
`String`-representation of the incoming operation that it will act upon. In the
event that this `String` was not directly passed in from the client, this
may be retrieved from a cache store (e.g., Automated Persisted Queries).

At this stage, there is not a guarantee that the operation is not malformed.

```typescript
didResolveSource?(
requestContext: WithRequired<
GraphQLRequestContext<TContext>, 'source' | 'logger'>,
>,
): ValueOrPromise<void>;
```

### `parsingDidStart`

The `parsingDidStart` event fires whenever Apollo Server will parse a GraphQL
Expand Down
66 changes: 66 additions & 0 deletions packages/apollo-server-core/src/__tests__/runQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
GraphQLRequestExecutionListener,
GraphQLRequestListenerDidResolveField,
GraphQLRequestListenerExecutionDidEnd,
GraphQLRequestListenerParsingDidEnd,
GraphQLRequestListenerValidationDidEnd,
} from 'apollo-server-plugin-base';
import { GraphQLRequestListener } from 'apollo-server-plugin-base';
import { InMemoryLRUCache } from 'apollo-server-caching';
Expand Down Expand Up @@ -477,6 +479,39 @@ describe('runQuery', () => {
});
});

/**
* This tests the simple invocation of the "didResolveSource" hook, but
* doesn't test one of the primary reasons why "source" isn't guaranteed
* sooner in the request life-cycle: when "source" is populated via an APQ
* cache HIT.
*
* That functionality is tested in `apollo-server-integration-testsuite`,
* within the "Persisted Queries" tests. (Search for "didResolveSource").
*/
describe('didResolveSource', () => {
const didResolveSource = jest.fn();
it('called with the source', async () => {
await runQuery({
schema,
queryString: '{ testString }',
plugins: [
{
requestDidStart() {
return {
didResolveSource,
};
},
},
],
request: new MockReq(),
});

expect(didResolveSource).toHaveBeenCalled();
expect(didResolveSource.mock.calls[0][0])
.toHaveProperty('source', '{ testString }');
});
});

describe('parsingDidStart', () => {
const parsingDidStart = jest.fn();
it('called when parsing will result in an error', async () => {
Expand Down Expand Up @@ -864,6 +899,25 @@ describe('runQuery', () => {
let stopAwaiting: Function;
const toBeAwaited = new Promise(resolve => stopAwaiting = resolve);

const parsingDidEnd: GraphQLRequestListenerParsingDidEnd =
jest.fn(() => callOrder.push('parsingDidEnd'));
const parsingDidStart: GraphQLRequestListener['parsingDidStart'] =
jest.fn(() => {
callOrder.push('parsingDidStart');
return parsingDidEnd;
});

const validationDidEnd: GraphQLRequestListenerValidationDidEnd =
jest.fn(() => callOrder.push('validationDidEnd'));
const validationDidStart: GraphQLRequestListener['validationDidStart'] =
jest.fn(() => {
callOrder.push('validationDidStart');
return validationDidEnd;
});

const didResolveSource: GraphQLRequestListener['didResolveSource'] =
jest.fn(() => { callOrder.push('didResolveSource') });

const didResolveField: GraphQLRequestListenerDidResolveField =
jest.fn(() => callOrder.push("didResolveField"));

Expand Down Expand Up @@ -908,6 +962,9 @@ describe('runQuery', () => {
{
requestDidStart() {
return {
parsingDidStart,
validationDidStart,
didResolveSource,
executionDidStart,
};
},
Expand All @@ -916,10 +973,19 @@ describe('runQuery', () => {
request: new MockReq(),
});

expect(parsingDidStart).toHaveBeenCalledTimes(1);
expect(parsingDidEnd).toHaveBeenCalledTimes(1);
expect(validationDidStart).toHaveBeenCalledTimes(1);
expect(validationDidEnd).toHaveBeenCalledTimes(1);
expect(executionDidStart).toHaveBeenCalledTimes(1);
expect(willResolveField).toHaveBeenCalledTimes(1);
expect(didResolveField).toHaveBeenCalledTimes(1);
expect(callOrder).toStrictEqual([
"didResolveSource",
"parsingDidStart",
"parsingDidEnd",
"validationDidStart",
"validationDidEnd",
"executionDidStart",
"willResolveField",
"beforeAwaiting",
Expand Down
11 changes: 11 additions & 0 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
import {
ApolloServerPlugin,
GraphQLRequestListener,
GraphQLRequestContextDidResolveSource,
GraphQLRequestContextExecutionDidStart,
GraphQLRequestContextResponseForOperation,
GraphQLRequestContextDidResolveOperation,
Expand Down Expand Up @@ -203,6 +204,16 @@ export async function processGraphQLRequest<TContext>(
requestContext.queryHash = queryHash;
requestContext.source = query;

// Let the plugins know that we now have a STRING of what we hope will
// parse and validate into a document we can execute on. Unless we have
// retrieved this from our APQ cache, there's no guarantee that it is
// syntactically correct, so this string should not be trusted as a valid
// document until after it's parsed and validated.
await dispatcher.invokeHookAsync(
'didResolveSource',
requestContext as GraphQLRequestContextDidResolveSource<TContext>,
);

const requestDidEnd = extensionStack.requestDidStart({
request: request.http!,
queryString: request.query,
Expand Down
Loading

0 comments on commit 4306673

Please sign in to comment.