From 75d83b8c5f97d59b768892d35461bbb98b3da4aa Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 20 Aug 2018 13:31:48 -0400 Subject: [PATCH] Allow an optional function to resolve the rootValue Passes the parsed DocumentNode AST to determine the root value, useful when providing a different rootValue for query vs mutation --- CHANGELOG.md | 1 + .../src/__tests__/runQuery.test.ts | 17 +++++++++ .../apollo-server-core/src/graphqlOptions.ts | 8 +++-- packages/apollo-server-core/src/runQuery.ts | 7 ++-- .../src/index.ts | 36 +++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f539d6f08..8a1f714f9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All of the packages in the `apollo-server` repo are released with the same versi ### vNEXT - Google Cloud Function support [#1402](https://github.com/apollographql/apollo-server/issues/1402) +- Allow an optional function to resolve the `rootValue`, passing the `DocumentNode` AST to determine the value. ### v2.0.4 diff --git a/packages/apollo-server-core/src/__tests__/runQuery.test.ts b/packages/apollo-server-core/src/__tests__/runQuery.test.ts index cc3ea0e98ab..895d4295576 100644 --- a/packages/apollo-server-core/src/__tests__/runQuery.test.ts +++ b/packages/apollo-server-core/src/__tests__/runQuery.test.ts @@ -8,6 +8,7 @@ import { GraphQLInt, GraphQLNonNull, parse, + DocumentNode, } from 'graphql'; import { runQuery } from '../runQuery'; @@ -175,6 +176,22 @@ describe('runQuery', () => { }); }); + it('correctly evaluates a rootValue function', () => { + const query = `{ testRootValue }`; + const expected = { testRootValue: 'it also works' }; + return runQuery({ + schema, + queryString: query, + rootValue: (doc: DocumentNode) => { + expect(doc.kind).toEqual('Document'); + return 'it also'; + }, + request: new MockReq(), + }).then(res => { + expect(res.data).toEqual(expected); + }); + }); + it('correctly passes in the context', () => { const query = `{ testContextValue }`; const expected = { testContextValue: 'it still works' }; diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index 4f45e08d57e..4c0673515bd 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -2,6 +2,7 @@ import { GraphQLSchema, ValidationContext, GraphQLFieldResolver, + DocumentNode, } from 'graphql'; import { GraphQLExtension } from 'graphql-extensions'; import { CacheControlExtensionOptions } from 'apollo-cache-control'; @@ -13,7 +14,7 @@ import { DataSource } from 'apollo-datasource'; * * - schema: an executable GraphQL schema used to fulfill requests. * - (optional) formatError: Formatting function applied to all errors before response is sent - * - (optional) rootValue: rootValue passed to GraphQL execution + * - (optional) rootValue: rootValue passed to GraphQL execution, or a function to resolving the rootValue from the DocumentNode * - (optional) context: the context passed to GraphQL execution * - (optional) validationRules: extra validation rules applied to requests * - (optional) formatResponse: a function applied to each graphQL execution result @@ -25,11 +26,12 @@ import { DataSource } from 'apollo-datasource'; export interface GraphQLServerOptions< TContext = | (() => Promise> | Record) - | Record + | Record, + TRootVal = ((parsedQuery: DocumentNode) => any) | any > { schema: GraphQLSchema; formatError?: Function; - rootValue?: any; + rootValue?: TRootVal; context?: TContext; validationRules?: Array<(context: ValidationContext) => any>; formatResponse?: Function; diff --git a/packages/apollo-server-core/src/runQuery.ts b/packages/apollo-server-core/src/runQuery.ts index 12c33fbbaa1..baf005930ea 100644 --- a/packages/apollo-server-core/src/runQuery.ts +++ b/packages/apollo-server-core/src/runQuery.ts @@ -50,7 +50,7 @@ export interface QueryOptions { // a mutation), throw this error. nonQueryError?: Error; - rootValue?: any; + rootValue?: ((parsedQuery: DocumentNode) => any) | any; context?: any; variables?: { [key: string]: any }; operationName?: string; @@ -219,7 +219,10 @@ function doRunQuery(options: QueryOptions): Promise { const executionArgs: ExecutionArgs = { schema: options.schema, document: documentAST, - rootValue: options.rootValue, + rootValue: + typeof options.rootValue === 'function' + ? options.rootValue(documentAST) + : options.rootValue, contextValue: context, variableValues: options.variables, operationName: options.operationName, diff --git a/packages/apollo-server-integration-testsuite/src/index.ts b/packages/apollo-server-integration-testsuite/src/index.ts index 36d7325780b..e73e77a9493 100644 --- a/packages/apollo-server-integration-testsuite/src/index.ts +++ b/packages/apollo-server-integration-testsuite/src/index.ts @@ -12,6 +12,8 @@ import { GraphQLScalarType, introspectionQuery, BREAK, + DocumentNode, + getOperationAST, } from 'graphql'; import request = require('supertest'); @@ -842,6 +844,40 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => { }); }); + it('passes the rootValue function result to the resolver', async () => { + const expectedQuery = 'query: it passes rootValue'; + const expectedMutation = 'mutation: it passes rootValue'; + app = await createApp({ + graphqlOptions: { + schema, + rootValue: (documentNode: DocumentNode) => { + const op = getOperationAST(documentNode, undefined); + return op.operation === 'query' + ? expectedQuery + : expectedMutation; + }, + }, + }); + const queryReq = request(app) + .post('/graphql') + .send({ + query: 'query test{ testRootValue }', + }); + return queryReq.then(res => { + expect(res.status).toEqual(200); + expect(res.body.data.testRootValue).toEqual(expectedQuery); + }); + const mutationReq = request(app) + .post('/graphql') + .send({ + query: 'mutation test{ testMutation(echo: "ping") }', + }); + return mutationReq.then(res => { + expect(res.status).toEqual(200); + expect(res.body.data.testRootValue).toEqual(expectedMutation); + }); + }); + it('returns errors', async () => { const expected = 'Secret error message'; app = await createApp({