diff --git a/src/__tests__/starWarsQueryTests.js b/src/__tests__/starWarsQueryTests.js index f838926fd8..180e12b3af 100644 --- a/src/__tests__/starWarsQueryTests.js +++ b/src/__tests__/starWarsQueryTests.js @@ -181,7 +181,7 @@ describe('Star Wars Query Tests', () => { name: 'Luke Skywalker' } }; - const result = await graphql(StarWarsSchema, query, null, params); + const result = await graphql(StarWarsSchema, query, null, null, params); expect(result).to.deep.equal({ data: expected }); }); @@ -201,7 +201,7 @@ describe('Star Wars Query Tests', () => { name: 'Han Solo' } }; - const result = await graphql(StarWarsSchema, query, null, params); + const result = await graphql(StarWarsSchema, query, null, null, params); expect(result).to.deep.equal({ data: expected }); }); @@ -219,7 +219,7 @@ describe('Star Wars Query Tests', () => { const expected = { human: null }; - const result = await graphql(StarWarsSchema, query, null, params); + const result = await graphql(StarWarsSchema, query, null, null, params); expect(result).to.deep.equal({ data: expected }); }); }); diff --git a/src/execution/__tests__/executor.js b/src/execution/__tests__/executor.js index 7df25e0c08..f15277b1ad 100644 --- a/src/execution/__tests__/executor.js +++ b/src/execution/__tests__/executor.js @@ -137,7 +137,7 @@ describe('Execute: Handles basic execution tasks', () => { }); expect( - await execute(schema, ast, data, { size: 100 }, 'Example') + await execute(schema, ast, data, null, { size: 100 }, 'Example') ).to.deep.equal(expected); }); @@ -427,7 +427,7 @@ describe('Execute: Handles basic execution tasks', () => { }) }); - const result = await execute(schema, ast, data, null, 'OtherExample'); + const result = await execute(schema, ast, data, null, null, 'OtherExample'); expect(result).to.deep.equal({ data: { second: 'b' } }); }); @@ -481,7 +481,9 @@ describe('Execute: Handles basic execution tasks', () => { }) }); - expect(() => execute(schema, ast, data, null, 'UnknownExample')).to.throw( + expect(() => + execute(schema, ast, data, null, null, 'UnknownExample') + ).to.throw( 'Unknown operation named "UnknownExample".' ); }); @@ -511,7 +513,7 @@ describe('Execute: Handles basic execution tasks', () => { }) }); - const queryResult = await execute(schema, ast, data, {}, 'Q'); + const queryResult = await execute(schema, ast, data, null, {}, 'Q'); expect(queryResult).to.deep.equal({ data: { a: 'b' } }); }); @@ -535,7 +537,7 @@ describe('Execute: Handles basic execution tasks', () => { }) }); - const mutationResult = await execute(schema, ast, data, {}, 'M'); + const mutationResult = await execute(schema, ast, data, null, {}, 'M'); expect(mutationResult).to.deep.equal({ data: { c: 'd' } }); }); @@ -559,7 +561,7 @@ describe('Execute: Handles basic execution tasks', () => { }) }); - const subscriptionResult = await execute(schema, ast, data, {}, 'S'); + const subscriptionResult = await execute(schema, ast, data, null, {}, 'S'); expect(subscriptionResult).to.deep.equal({ data: { a: 'b' } }); }); @@ -644,7 +646,7 @@ describe('Execute: Handles basic execution tasks', () => { }), }); - const queryResult = await execute(schema, ast, data, {}, 'Q'); + const queryResult = await execute(schema, ast, data, null, {}, 'Q'); expect(queryResult).to.deep.equal({ data: { a: 'b' } }); }); diff --git a/src/execution/__tests__/union-interface.js b/src/execution/__tests__/union-interface.js index 0156e20c73..5797ca07f5 100644 --- a/src/execution/__tests__/union-interface.js +++ b/src/execution/__tests__/union-interface.js @@ -349,6 +349,7 @@ describe('Execute: Union and intersection types', () => { }); it('gets execution info in resolver', async () => { + let encounteredContext; let encounteredSchema; let encounteredRootValue; @@ -357,9 +358,10 @@ describe('Execute: Union and intersection types', () => { fields: { name: { type: GraphQLString } }, - resolveType(obj, { schema: infoSchema, rootValue: infoRootValue }) { - encounteredSchema = infoSchema; - encounteredRootValue = infoRootValue; + resolveType(obj, context, { schema: _schema, rootValue }) { + encounteredContext = context; + encounteredSchema = _schema; + encounteredRootValue = rootValue; return PersonType2; } }); @@ -379,14 +381,17 @@ describe('Execute: Union and intersection types', () => { const john2 = new Person('John', [], [ liz ]); + const context = { authToken: '123abc' }; + const ast = parse('{ name, friends { name } }'); expect( - await execute(schema2, ast, john2) + await execute(schema2, ast, john2, context) ).to.deep.equal({ data: { name: 'John', friends: [ { name: 'Liz' } ] } }); + expect(encounteredContext).to.equal(context); expect(encounteredSchema).to.equal(schema2); expect(encounteredRootValue).to.equal(john2); }); diff --git a/src/execution/__tests__/variables.js b/src/execution/__tests__/variables.js index d4d67e1477..01b649766a 100644 --- a/src/execution/__tests__/variables.js +++ b/src/execution/__tests__/variables.js @@ -204,7 +204,7 @@ describe('Execute: Handles inputs', () => { it('executes with complex input', async () => { const params = { input: { a: 'foo', b: [ 'bar' ], c: 'baz' } }; - const result = await execute(schema, ast, null, params); + const result = await execute(schema, ast, null, null, params); return expect(result).to.deep.equal({ data: { @@ -231,7 +231,7 @@ describe('Execute: Handles inputs', () => { it('properly parses single value to list', async () => { const params = { input: { a: 'foo', b: 'bar', c: 'baz' } }; - const result = await execute(schema, ast, null, params); + const result = await execute(schema, ast, null, null, params); return expect(result).to.deep.equal({ data: { @@ -242,7 +242,7 @@ describe('Execute: Handles inputs', () => { it('executes with complex scalar input', async () => { const params = { input: { c: 'foo', d: 'SerializedValue' } }; - const result = await execute(schema, ast, null, params); + const result = await execute(schema, ast, null, null, params); return expect(result).to.deep.equal({ data: { @@ -256,7 +256,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, params); + execute(schema, ast, null, null, params); } catch (error) { caughtError = error; } @@ -275,7 +275,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, params); + execute(schema, ast, null, null, params); } catch (error) { caughtError = error; } @@ -293,7 +293,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, params); + execute(schema, ast, null, null, params); } catch (error) { caughtError = error; } @@ -317,7 +317,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, nestedAst, null, params); + execute(schema, nestedAst, null, null, params); } catch (error) { caughtError = error; } @@ -339,7 +339,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, params); + execute(schema, ast, null, null, params); } catch (error) { caughtError = error; } @@ -411,7 +411,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { value: null }) + await execute(schema, ast, null, null, { value: null }) ).to.deep.equal({ data: { fieldWithNullableStringInput: null @@ -428,7 +428,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { value: 'a' }) + await execute(schema, ast, null, null, { value: 'a' }) ).to.deep.equal({ data: { fieldWithNullableStringInput: '"a"' @@ -484,7 +484,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, { value: null }); + execute(schema, ast, null, null, { value: null }); } catch (error) { caughtError = error; } @@ -505,7 +505,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { value: 'a' }) + await execute(schema, ast, null, null, { value: 'a' }) ).to.deep.equal({ data: { fieldWithNonNullableStringInput: '"a"' @@ -554,7 +554,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: null }) + await execute(schema, ast, null, null, { input: null }) ).to.deep.equal({ data: { list: null @@ -571,7 +571,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A' ] }) + await execute(schema, ast, null, null, { input: [ 'A' ] }) ).to.deep.equal({ data: { list: '["A"]' @@ -588,7 +588,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A', null, 'B' ] }) + await execute(schema, ast, null, null, { input: [ 'A', null, 'B' ] }) ).to.deep.equal({ data: { list: '["A",null,"B"]' @@ -606,7 +606,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, { input: null }); + execute(schema, ast, null, null, { input: null }); } catch (error) { caughtError = error; } @@ -627,7 +627,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A' ] }) + await execute(schema, ast, null, null, { input: [ 'A' ] }) ).to.deep.equal({ data: { nnList: '["A"]' @@ -644,7 +644,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A', null, 'B' ] }) + await execute(schema, ast, null, null, { input: [ 'A', null, 'B' ] }) ).to.deep.equal({ data: { nnList: '["A",null,"B"]' @@ -661,7 +661,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: null }) + await execute(schema, ast, null, null, { input: null }) ).to.deep.equal({ data: { listNN: null @@ -678,7 +678,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A' ] }) + await execute(schema, ast, null, null, { input: [ 'A' ] }) ).to.deep.equal({ data: { listNN: '["A"]' @@ -697,7 +697,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, vars); + execute(schema, ast, null, null, vars); } catch (error) { caughtError = error; } @@ -720,7 +720,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, { input: null }); + execute(schema, ast, null, null, { input: null }); } catch (error) { caughtError = error; } @@ -741,7 +741,7 @@ describe('Execute: Handles inputs', () => { const ast = parse(doc); return expect( - await execute(schema, ast, null, { input: [ 'A' ] }) + await execute(schema, ast, null, null, { input: [ 'A' ] }) ).to.deep.equal({ data: { nnListNN: '["A"]' @@ -760,7 +760,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, vars); + execute(schema, ast, null, null, vars); } catch (error) { caughtError = error; } @@ -784,7 +784,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, vars); + execute(schema, ast, null, null, vars); } catch (error) { caughtError = error; } @@ -808,7 +808,7 @@ describe('Execute: Handles inputs', () => { let caughtError; try { - execute(schema, ast, null, vars); + execute(schema, ast, null, null, vars); } catch (error) { caughtError = error; } diff --git a/src/execution/execute.js b/src/execution/execute.js index 3d20d1be03..e51555b246 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -30,6 +30,7 @@ import type { GraphQLLeafType, GraphQLAbstractType, GraphQLFieldDefinition, + GraphQLFieldResolveFn, GraphQLResolveInfo, } from '../type/definition'; import { GraphQLSchema } from '../type/schema'; @@ -83,6 +84,7 @@ type ExecutionContext = { schema: GraphQLSchema; fragments: {[key: string]: FragmentDefinition}; rootValue: mixed; + contextValue: mixed; operation: OperationDefinition; variableValues: {[key: string]: mixed}; errors: Array; @@ -110,6 +112,7 @@ export function execute( schema: GraphQLSchema, documentAST: Document, rootValue?: mixed, + contextValue?: mixed, variableValues?: ?{[key: string]: mixed}, operationName?: ?string ): Promise { @@ -126,6 +129,7 @@ export function execute( schema, documentAST, rootValue, + contextValue, variableValues, operationName ); @@ -163,6 +167,7 @@ function buildExecutionContext( schema: GraphQLSchema, documentAST: Document, rootValue: mixed, + contextValue: mixed, rawVariableValues: ?{[key: string]: mixed}, operationName: ?string ): ExecutionContext { @@ -203,9 +208,16 @@ function buildExecutionContext( operation.variableDefinitions || [], rawVariableValues || {} ); - const exeContext: ExecutionContext = - { schema, fragments, rootValue, operation, variableValues, errors }; - return exeContext; + + return { + schema, + fragments, + rootValue, + contextValue, + operation, + variableValues, + errors + }; } /** @@ -533,7 +545,12 @@ function resolveField( exeContext.variableValues ); - // The resolve function's optional third argument is a collection of + // The resolve function's optional third argument is a context value that + // is provided to every resolve function within an execution. It is commonly + // used to represent an authenticated user, or request-specific caches. + const context = exeContext.contextValue; + + // The resolve function's optional fourth argument is a collection of // information about the current execution state. const info: GraphQLResolveInfo = { fieldName, @@ -549,7 +566,7 @@ function resolveField( // Get the resolve function, regardless of if its result is normal // or abrupt (error). - const result = resolveOrError(resolveFn, source, args, info); + const result = resolveOrError(resolveFn, source, args, context, info); return completeValueCatchingError( exeContext, @@ -562,18 +579,15 @@ function resolveField( // Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` // function. Returns the result of resolveFn or the abrupt-return Error object. -function resolveOrError( - resolveFn: ( - source: mixed, - args: { [key: string]: mixed }, - info: GraphQLResolveInfo - ) => T, +function resolveOrError( + resolveFn: GraphQLFieldResolveFn, source: mixed, args: { [key: string]: mixed }, + context: mixed, info: GraphQLResolveInfo -): Error | T { +): Error | mixed { try { - return resolveFn(source, args, info); + return resolveFn(source, args, context, info); } catch (error) { // Sometimes a non-error is thrown, wrap it as an Error for a // consistent interface. @@ -800,8 +814,8 @@ function completeAbstractValue( result: mixed ): mixed { const runtimeType = returnType.resolveType ? - returnType.resolveType(result, info) : - defaultResolveTypeFn(result, info, returnType); + returnType.resolveType(result, exeContext.contextValue, info) : + defaultResolveTypeFn(result, exeContext.contextValue, info, returnType); if (!runtimeType) { return null; @@ -838,7 +852,8 @@ function completeObjectValue( // If there is an isTypeOf predicate function, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. - if (returnType.isTypeOf && !returnType.isTypeOf(result, info)) { + if (returnType.isTypeOf && + !returnType.isTypeOf(result, exeContext.contextValue, info)) { throw new GraphQLError( `Expected value of type "${returnType}" but got: ${result}.`, fieldASTs @@ -871,13 +886,14 @@ function completeObjectValue( */ function defaultResolveTypeFn( value: mixed, + context: mixed, info: GraphQLResolveInfo, abstractType: GraphQLAbstractType ): ?GraphQLObjectType { const possibleTypes = info.schema.getPossibleTypes(abstractType); for (let i = 0; i < possibleTypes.length; i++) { const type = possibleTypes[i]; - if (typeof type.isTypeOf === 'function' && type.isTypeOf(value, info)) { + if (type.isTypeOf && type.isTypeOf(value, context, info)) { return type; } } @@ -889,7 +905,7 @@ function defaultResolveTypeFn( * and returns it as the result, or if it's a function, returns the result * of calling that function. */ -function defaultResolveFn(source: any, args, { fieldName }) { +function defaultResolveFn(source: any, args, context, { fieldName }) { // ensure source is a value for which property access is acceptable. if (typeof source === 'object' || typeof source === 'function') { const property = source[fieldName]; diff --git a/src/graphql.js b/src/graphql.js index cdc3bdda9b..4e4a764c73 100644 --- a/src/graphql.js +++ b/src/graphql.js @@ -44,6 +44,7 @@ export function graphql( schema: GraphQLSchema, requestString: string, rootValue?: mixed, + contextValue?: mixed, variableValues?: ?{[key: string]: mixed}, operationName?: ?string ): Promise { @@ -59,6 +60,7 @@ export function graphql( schema, documentAST, rootValue, + contextValue, variableValues, operationName ) diff --git a/src/type/__tests__/enumType.js b/src/type/__tests__/enumType.js index 75f4800f5d..7c55f514fc 100644 --- a/src/type/__tests__/enumType.js +++ b/src/type/__tests__/enumType.js @@ -172,6 +172,7 @@ describe('Type System: Enum Values', () => { schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', null, + null, { color: 'BLUE' } ) ).to.deep.equal({ @@ -187,6 +188,7 @@ describe('Type System: Enum Values', () => { schema, 'mutation x($color: Color!) { favoriteEnum(color: $color) }', null, + null, { color: 'GREEN' } ) ).to.deep.equal({ @@ -202,6 +204,7 @@ describe('Type System: Enum Values', () => { schema, 'subscription x($color: Color!) { subscribeToEnum(color: $color) }', null, + null, { color: 'GREEN' } ) ).to.deep.equal({ @@ -217,6 +220,7 @@ describe('Type System: Enum Values', () => { schema, 'query test($color: Color!) { colorEnum(fromEnum: $color) }', null, + null, { color: 2 } ) ).to.deep.equal({ @@ -235,6 +239,7 @@ describe('Type System: Enum Values', () => { schema, 'query test($color: String!) { colorEnum(fromEnum: $color) }', null, + null, { color: 'BLUE' } ) ).to.deep.equal({ @@ -253,6 +258,7 @@ describe('Type System: Enum Values', () => { schema, 'query test($color: Int!) { colorEnum(fromEnum: $color) }', null, + null, { color: 2 } ) ).to.deep.equal({ diff --git a/src/type/definition.js b/src/type/definition.js index 64dcb952b1..70f0bf2a6e 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -456,17 +456,20 @@ type GraphQLFieldConfigMapThunk = () => GraphQLFieldConfigMap; export type GraphQLTypeResolveFn = ( value: mixed, + context: mixed, info: GraphQLResolveInfo ) => ?GraphQLObjectType export type GraphQLIsTypeOfFn = ( value: mixed, + context: mixed, info: GraphQLResolveInfo ) => boolean export type GraphQLFieldResolveFn = ( source: mixed, args: {[argName: string]: mixed}, + context: mixed, info: GraphQLResolveInfo ) => mixed diff --git a/src/type/introspection.js b/src/type/introspection.js index 61c7b1cb82..d760dac70f 100644 --- a/src/type/introspection.js +++ b/src/type/introspection.js @@ -218,7 +218,7 @@ export const __Type = new GraphQLObjectType({ }, possibleTypes: { type: new GraphQLList(new GraphQLNonNull(__Type)), - resolve(type, args, { schema }) { + resolve(type, args, context, { schema }) { if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) { return schema.getPossibleTypes(type); @@ -385,7 +385,7 @@ export const SchemaMetaFieldDef: GraphQLFieldDefinition = { type: new GraphQLNonNull(__Schema), description: 'Access the current type schema of this server.', args: [], - resolve: (source, args, { schema }) => schema + resolve: (source, args, context, { schema }) => schema }; export const TypeMetaFieldDef: GraphQLFieldDefinition = { @@ -395,7 +395,7 @@ export const TypeMetaFieldDef: GraphQLFieldDefinition = { args: [ { name: 'name', type: new GraphQLNonNull(GraphQLString) } ], - resolve: (source, { name }: { name: string }, { schema }) => + resolve: (source, { name }: { name: string }, context, { schema }) => schema.getType(name) }; @@ -404,5 +404,5 @@ export const TypeNameMetaFieldDef: GraphQLFieldDefinition = { type: new GraphQLNonNull(GraphQLString), description: 'The name of the current Object type at runtime.', args: [], - resolve: (source, args, { parentType }) => parentType.name + resolve: (source, args, context, { parentType }) => parentType.name }; diff --git a/src/utilities/__tests__/buildClientSchema.js b/src/utilities/__tests__/buildClientSchema.js index fcfb49e705..b9ede38c61 100644 --- a/src/utilities/__tests__/buildClientSchema.js +++ b/src/utilities/__tests__/buildClientSchema.js @@ -672,6 +672,7 @@ describe('Type System: build schema from introspection', () => { clientSchema, 'query NoNo($v: CustomScalar) { foo(custom1: 123, custom2: $v) }', { foo: 'bar' }, + null, { v: 'baz' } ); expect(result).to.containSubset({