Pass the context request and response extension methods (#1547)

* Pass the context along to all the extension methods

Addresses #1343

With this change you should now be able to implement an extension like so:

```javascript
class MyErrorTrackingExtension extends GraphQLExtension {
    willSendResponse(o) {
        const { context, graphqlResponse } = o

        context.trackErrors(graphqlResponse.errors)

        return o
    }
}
```

Edit by @evans :
fixes #1343
fixes #614 as the request object can be taken from context or from requestDidStart
fixes #631 ""

* Remove context from extra extension functions

The context shouldn't be necessary for format, parse, validation, and
execute. Format generally outputs the state of the extension. Parse and
validate don't depend on the context. Execution includes the context
argument as contextValue

* Move change entry under vNext
This commit is contained in:
C.J. Winslow 2018-09-07 15:10:30 -10:00 committed by Jesse Rosenberger
parent e4d672c568
commit 84bc8346cc
No known key found for this signature in database
GPG key ID: C0CCCF81AA6C08D8
6 changed files with 44 additions and 12 deletions

View file

@ -2,7 +2,6 @@
### vNEXT
- FIXME(@abernix): Allow context to be passed to all GraphQLExtension methods.
- Allow an optional function to resolve the `rootValue`, passing the `DocumentNode` AST to determine the value. [PR #1555](https://github.com/apollographql/apollo-server/pull/1555)
- Follow-up on the work in [PR #1516](https://github.com/apollographql/apollo-server/pull/1516) to also fix missing insertion cursor/caret when a custom GraphQL configuration is specified which doesn't specify its own `cursorShape` property. [PR #1607](https://github.com/apollographql/apollo-server/pull/1607)

View file

@ -68,7 +68,7 @@ export class EngineReportingExtension<TContext = any>
request: Request;
queryString?: string;
parsedQuery?: DocumentNode;
variables: Record<string, any>;
variables?: Record<string, any>;
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
context: any;
@ -153,7 +153,7 @@ export class EngineReportingExtension<TContext = any>
} else {
try {
this.trace.details!.variablesJson![name] = JSON.stringify(
o.variables[name],
o.variables![name],
);
} catch (e) {
// This probably means that the value contains a circular reference,

View file

@ -435,6 +435,28 @@ describe('runQuery', () => {
});
});
});
it('runs willSendResponse with extensions context', async () => {
class CustomExtension implements GraphQLExtension<any> {
willSendResponse(o: any) {
expect(o).toHaveProperty('context.baz', 'always here');
return o;
}
}
const queryString = `{ testString }`;
const expected = { testString: 'it works' };
const extensions = [() => new CustomExtension()];
return runQuery({
schema,
queryString,
context: { baz: 'always here' },
extensions,
request: new MockReq(),
}).then(res => {
expect(res.data).toEqual(expected);
});
});
});
describe('async_hooks', () => {

View file

@ -1,7 +1,7 @@
import { GraphQLExtension, GraphQLResponse } from 'graphql-extensions';
import { formatApolloErrors } from 'apollo-server-errors';
export class FormatErrorExtension extends GraphQLExtension {
export class FormatErrorExtension<TContext = any> extends GraphQLExtension {
private formatError?: Function;
private debug: boolean;
@ -13,9 +13,11 @@ export class FormatErrorExtension extends GraphQLExtension {
public willSendResponse(o: {
graphqlResponse: GraphQLResponse;
}): void | { graphqlResponse: GraphQLResponse } {
context: TContext;
}): void | { graphqlResponse: GraphQLResponse; context: TContext } {
if (o.graphqlResponse.errors) {
return {
...o,
graphqlResponse: {
...o.graphqlResponse,
errors: formatApolloErrors(o.graphqlResponse.errors, {

View file

@ -547,8 +547,11 @@ export function testApolloServer<AS extends ApolloServerBase>(
}
});
class Extension extends GraphQLExtension {
willSendResponse(o: { graphqlResponse: GraphQLResponse }) {
class Extension<TContext = any> extends GraphQLExtension {
willSendResponse(o: {
graphqlResponse: GraphQLResponse;
context: TContext;
}) {
expect(o.graphqlResponse.errors.length).toEqual(1);
// formatError should be called after extensions
expect(formatError).not.toBeCalled();
@ -636,8 +639,11 @@ export function testApolloServer<AS extends ApolloServerBase>(
return error;
});
class Extension extends GraphQLExtension {
willSendResponse(_o: { graphqlResponse: GraphQLResponse }) {
class Extension<TContext = any> extends GraphQLExtension {
willSendResponse(_o: {
graphqlResponse: GraphQLResponse;
context: TContext;
}) {
// formatError should be called after extensions
expect(formatError).not.toBeCalled();
extension();

View file

@ -33,6 +33,7 @@ export class GraphQLExtension<TContext = any> {
variables?: { [key: string]: any };
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
context: TContext;
}): EndHandler | void;
public parsingDidStart?(o: { queryString: string }): EndHandler | void;
public validationDidStart?(): EndHandler | void;
@ -42,7 +43,8 @@ export class GraphQLExtension<TContext = any> {
public willSendResponse?(o: {
graphqlResponse: GraphQLResponse;
}): void | { graphqlResponse: GraphQLResponse };
context: TContext;
}): void | { graphqlResponse: GraphQLResponse; context: TContext };
public willResolveField?(
source: any,
@ -71,7 +73,7 @@ export class GraphQLExtensionStack<TContext = any> {
variables?: { [key: string]: any };
persistedQueryHit?: boolean;
persistedQueryRegister?: boolean;
extensions?: Record<string, any>;
context: TContext;
}): EndHandler {
return this.handleDidStart(
ext => ext.requestDidStart && ext.requestDidStart(o),
@ -98,7 +100,8 @@ export class GraphQLExtensionStack<TContext = any> {
public willSendResponse(o: {
graphqlResponse: GraphQLResponse;
}): { graphqlResponse: GraphQLResponse } {
context: TContext;
}): { graphqlResponse: GraphQLResponse; context: TContext } {
let reference = o;
// Reverse the array, since this is functions as an end handler
[...this.extensions].reverse().forEach(extension => {