From b6a32ae4ef60de35559c0a871da64959a5063f3d Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Sun, 29 Apr 2018 11:21:47 +0300 Subject: [PATCH 1/2] Make 'defaultResolveType' to match GraphQLTypeResolver type. --- src/execution/execute.js | 21 +++++++++++---------- src/type/definition.js | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/execution/execute.js b/src/execution/execute.js index cc50828d25..6c893b4de7 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -44,6 +44,7 @@ import type { GraphQLField, GraphQLFieldResolver, GraphQLResolveInfo, + GraphQLTypeResolver, ResponsePath, GraphQLList, } from '../type/definition'; @@ -991,9 +992,9 @@ function completeAbstractValue( path: ResponsePath, result: mixed, ): MaybePromise> { - const runtimeType = returnType.resolveType - ? returnType.resolveType(result, exeContext.contextValue, info) - : defaultResolveTypeFn(result, exeContext.contextValue, info, returnType); + const resolveTypeFn = returnType.resolveType || defaultResolveType; + const contextValue = exeContext.contextValue; + const runtimeType = resolveTypeFn(result, contextValue, info, returnType); if (isPromise(runtimeType)) { return runtimeType.then(resolvedRuntimeType => @@ -1174,12 +1175,12 @@ function _collectSubfields( * Otherwise, test each possible type for the abstract type by calling * isTypeOf for the object being coerced, returning the first type that matches. */ -function defaultResolveTypeFn( - value: mixed, - contextValue: mixed, - info: GraphQLResolveInfo, - abstractType: GraphQLAbstractType, -): MaybePromise { +const defaultResolveType: GraphQLTypeResolver = function( + value, + contextValue, + info, + abstractType, +) { // First, look for `__typename`. if ( value !== null && @@ -1216,7 +1217,7 @@ function defaultResolveTypeFn( } }); } -} +}; /** * If a resolve function is not given, then a default resolve behavior is used diff --git a/src/type/definition.js b/src/type/definition.js index 05332f72b1..fb98531d81 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -832,6 +832,7 @@ export type GraphQLTypeResolver = ( value: TSource, context: TContext, info: GraphQLResolveInfo, + abstractType: GraphQLAbstractType, ) => MaybePromise; export type GraphQLIsTypeOfFn = ( From 3b60a358691e9b25bd2e8d4ed8eadfe006d67aba Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 1 May 2018 21:27:21 +0300 Subject: [PATCH 2/2] Allow providing custom default type resolver --- src/execution/__tests__/executor-test.js | 44 ++++++++++++++++++++++++ src/execution/execute.js | 14 ++++++-- src/execution/index.js | 7 +++- src/graphql.js | 16 ++++++++- src/index.js | 1 + 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/execution/__tests__/executor-test.js b/src/execution/__tests__/executor-test.js index 0d4014d3f7..acee86393f 100644 --- a/src/execution/__tests__/executor-test.js +++ b/src/execution/__tests__/executor-test.js @@ -15,6 +15,7 @@ import { execute } from '../execute'; import { Kind, parse } from '../../language'; import { GraphQLSchema, + GraphQLInterfaceType, GraphQLObjectType, GraphQLList, GraphQLBoolean, @@ -1029,4 +1030,47 @@ describe('Execute: Handles basic execution tasks', () => { const result = execute({ schema, document, fieldResolver }); expect(result).to.deep.equal({ data: { foo: 'foo' } }); }); + + it('uses a custom type resolver', () => { + const document = parse('{ foo { bar } }'); + + const fooInterface = new GraphQLInterfaceType({ + name: 'FooInterface', + fields: { + bar: { type: GraphQLString }, + }, + }); + + const fooObject = new GraphQLObjectType({ + name: 'FooObject', + interfaces: [fooInterface], + fields: { + bar: { type: GraphQLString }, + }, + }); + + const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: 'Query', + fields: { + foo: { type: fooInterface }, + }, + }), + types: [fooObject], + }); + + let possibleTypes; + function typeResolver(source, context, info, abstractType) { + // Resolver should be able to figure out all possible types on its own + possibleTypes = info.schema.getPossibleTypes(abstractType); + + return 'FooObject'; + } + + const rootValue = { foo: { bar: 'bar' } }; + const result = execute({ schema, document, rootValue, typeResolver }); + + expect(result).to.deep.equal({ data: { foo: { bar: 'bar' } } }); + expect(possibleTypes).to.deep.equal([fooObject]); + }); }); diff --git a/src/execution/execute.js b/src/execution/execute.js index 6c893b4de7..ccf2bf3d2f 100644 --- a/src/execution/execute.js +++ b/src/execution/execute.js @@ -103,6 +103,7 @@ export type ExecutionContext = {| operation: OperationDefinitionNode, variableValues: { [variable: string]: mixed }, fieldResolver: GraphQLFieldResolver, + typeResolver: GraphQLTypeResolver, errors: Array, |}; @@ -125,6 +126,7 @@ export type ExecutionArgs = {| variableValues?: ?{ [variable: string]: mixed }, operationName?: ?string, fieldResolver?: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, |}; /** @@ -152,6 +154,7 @@ declare function execute( variableValues?: ?{ [variable: string]: mixed }, operationName?: ?string, fieldResolver?: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, ): MaybePromise; export function execute( argsOrSchema, @@ -161,6 +164,7 @@ export function execute( variableValues, operationName, fieldResolver, + typeResolver, ) { /* eslint-enable no-redeclare */ // Extract arguments from object args if provided. @@ -173,6 +177,7 @@ export function execute( argsOrSchema.variableValues, argsOrSchema.operationName, argsOrSchema.fieldResolver, + argsOrSchema.typeResolver, ) : executeImpl( argsOrSchema, @@ -182,6 +187,7 @@ export function execute( variableValues, operationName, fieldResolver, + typeResolver, ); } @@ -193,6 +199,7 @@ function executeImpl( variableValues, operationName, fieldResolver, + typeResolver, ) { // If arguments are missing or incorrect, throw an error. assertValidExecutionArguments(schema, document, variableValues); @@ -207,6 +214,7 @@ function executeImpl( variableValues, operationName, fieldResolver, + typeResolver, ); // Return early errors if execution context failed. @@ -302,6 +310,7 @@ export function buildExecutionContext( rawVariableValues: ?ObjMap, operationName: ?string, fieldResolver: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, ): $ReadOnlyArray | ExecutionContext { const errors: Array = []; let operation: OperationDefinitionNode | void; @@ -372,6 +381,7 @@ export function buildExecutionContext( operation, variableValues, fieldResolver: fieldResolver || defaultFieldResolver, + typeResolver: typeResolver || defaultTypeResolver, errors, }; } @@ -992,7 +1002,7 @@ function completeAbstractValue( path: ResponsePath, result: mixed, ): MaybePromise> { - const resolveTypeFn = returnType.resolveType || defaultResolveType; + const resolveTypeFn = returnType.resolveType || exeContext.typeResolver; const contextValue = exeContext.contextValue; const runtimeType = resolveTypeFn(result, contextValue, info, returnType); @@ -1175,7 +1185,7 @@ function _collectSubfields( * Otherwise, test each possible type for the abstract type by calling * isTypeOf for the object being coerced, returning the first type that matches. */ -const defaultResolveType: GraphQLTypeResolver = function( +export const defaultTypeResolver: GraphQLTypeResolver = function( value, contextValue, info, diff --git a/src/execution/index.js b/src/execution/index.js index d793e2613a..19dc56eb62 100644 --- a/src/execution/index.js +++ b/src/execution/index.js @@ -7,7 +7,12 @@ * @flow strict */ -export { execute, defaultFieldResolver, responsePathAsArray } from './execute'; +export { + execute, + defaultFieldResolver, + defaultTypeResolver, + responsePathAsArray, +} from './execute'; export { getDirectiveValues } from './values'; export type { ExecutionArgs, ExecutionResult } from './execute'; diff --git a/src/graphql.js b/src/graphql.js index ab93016699..9624f224e4 100644 --- a/src/graphql.js +++ b/src/graphql.js @@ -13,7 +13,10 @@ import { validate } from './validation/validate'; import { execute } from './execution/execute'; import type { ObjMap } from './jsutils/ObjMap'; import type { Source } from './language/source'; -import type { GraphQLFieldResolver } from './type/definition'; +import type { + GraphQLFieldResolver, + GraphQLTypeResolver, +} from './type/definition'; import type { GraphQLSchema } from './type/schema'; import type { ExecutionResult } from './execution/execute'; import type { MaybePromise } from './jsutils/MaybePromise'; @@ -61,6 +64,7 @@ export type GraphQLArgs = {| variableValues?: ?ObjMap, operationName?: ?string, fieldResolver?: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, |}; declare function graphql(GraphQLArgs, ..._: []): Promise; /* eslint-disable no-redeclare */ @@ -72,6 +76,7 @@ declare function graphql( variableValues?: ?ObjMap, operationName?: ?string, fieldResolver?: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, ): Promise; export function graphql( argsOrSchema, @@ -81,6 +86,7 @@ export function graphql( variableValues, operationName, fieldResolver, + typeResolver, ) { /* eslint-enable no-redeclare */ // Always return a Promise for a consistent API. @@ -96,6 +102,7 @@ export function graphql( argsOrSchema.variableValues, argsOrSchema.operationName, argsOrSchema.fieldResolver, + argsOrSchema.typeResolver, ) : graphqlImpl( argsOrSchema, @@ -105,6 +112,7 @@ export function graphql( variableValues, operationName, fieldResolver, + typeResolver, ), ), ); @@ -126,6 +134,7 @@ declare function graphqlSync( variableValues?: ?ObjMap, operationName?: ?string, fieldResolver?: ?GraphQLFieldResolver, + typeResolver?: ?GraphQLTypeResolver, ): ExecutionResult; export function graphqlSync( argsOrSchema, @@ -135,6 +144,7 @@ export function graphqlSync( variableValues, operationName, fieldResolver, + typeResolver, ) { /* eslint-enable no-redeclare */ // Extract arguments from object args if provided. @@ -148,6 +158,7 @@ export function graphqlSync( argsOrSchema.variableValues, argsOrSchema.operationName, argsOrSchema.fieldResolver, + argsOrSchema.typeResolver, ) : graphqlImpl( argsOrSchema, @@ -157,6 +168,7 @@ export function graphqlSync( variableValues, operationName, fieldResolver, + typeResolver, ); // Assert that the execution was synchronous. @@ -175,6 +187,7 @@ function graphqlImpl( variableValues, operationName, fieldResolver, + typeResolver, ): MaybePromise { // Validate Schema const schemaValidationErrors = validateSchema(schema); @@ -205,5 +218,6 @@ function graphqlImpl( variableValues, operationName, fieldResolver, + typeResolver, ); } diff --git a/src/index.js b/src/index.js index bc143f0c7d..42b571e127 100644 --- a/src/index.js +++ b/src/index.js @@ -281,6 +281,7 @@ export type { export { execute, defaultFieldResolver, + defaultTypeResolver, responsePathAsArray, getDirectiveValues, } from './execution';