From 0b6363acf08cc95172a9c0bf2336032297737001 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Mon, 20 Dec 2021 18:21:37 +0200 Subject: [PATCH] allow type resolution with GraphQLObjectTypes (#117) ...as opposed to just type names. This reverts graphql#2905 This change was made upstream to support schema transformation functions like lexicographicallySortSchema which do not modify resolveType methods. Previously, a workaround was included that simply looks up the actual type from the type name returned by resolveType. In actuality, in my opinion, the schema transformation functions should natively or manually modify the resolveType function so it works correctly. In any case, we restore the workaround here to preserve v14 support. --- src/execution/__tests__/abstract-test.ts | 69 ++++++++++++++++++++---- src/execution/executor.ts | 20 +++---- src/index.ts | 1 + src/type/definition.ts | 15 ++++++ src/type/index.ts | 1 + 5 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 27b5a52587..071ed94dd5 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -562,7 +562,66 @@ describe('Execute: Handles execution of abstract types', () => { }); }); - it('resolveType on Interface yields useful error', () => { + it('resolve Interface type using GraphQLObjectType', async () => { + const schema = buildSchema(` + type Query { + pets: [Pet] + } + + interface Pet { + name: String + } + + type Cat implements Pet { + name: String + meows: Boolean + } + + type Dog implements Pet { + name: String + woofs: Boolean + } + `); + + const query = ` + { + pets { + name + ... on Cat { + meows + } + } + } + `; + + const rootValue = { + pets: [ + { + __typename: 'Cat', + name: 'Garfield', + meows: false, + }, + ], + }; + + // FIXME: workaround since we can't inject resolveType into SDL + // @ts-expect-error + assertInterfaceType(schema.getType('Pet')).resolveType = () => + schema.getType('Cat'); + + expect(await executeQuery({ schema, query, rootValue })).to.deep.equal({ + data: { + pets: [ + { + name: 'Garfield', + meows: false, + }, + ], + }, + }); + }); + + it('resolve Interface type using __typename on source object', () => { const schema = buildSchema(` type Query { pet: Pet @@ -630,13 +689,5 @@ describe('Execute: Handles execution of abstract types', () => { expectError({ forTypeName: undefined }).toEqual( 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".', ); - - // FIXME: workaround since we can't inject resolveType into SDL - // @ts-expect-error - assertInterfaceType(schema.getType('Pet')).resolveType = () => - schema.getType('Cat'); - expectError({ forTypeName: undefined }).toEqual( - 'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.', - ); }); }); diff --git a/src/execution/executor.ts b/src/execution/executor.ts index a604fc9634..04228fba7c 100644 --- a/src/execution/executor.ts +++ b/src/execution/executor.ts @@ -52,6 +52,7 @@ import { isAbstractType, isLeafType, isListType, + isNamedType, isNonNullType, isObjectType, } from '../type/definition'; @@ -1439,31 +1440,24 @@ export class Executor { } ensureValidRuntimeType( - runtimeTypeName: unknown, + runtimeTypeOrName: unknown, exeContext: ExecutionContext, returnType: GraphQLAbstractType, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, result: unknown, ): GraphQLObjectType { - if (runtimeTypeName == null) { + if (runtimeTypeOrName == null) { throw new GraphQLError( `Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${returnType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, fieldNodes, ); } - // releases before 16.0.0 supported returning `GraphQLObjectType` from `resolveType` - // TODO: remove in 17.0.0 release - if ( - typeof runtimeTypeName === 'object' && - runtimeTypeName && - isObjectType(runtimeTypeName) - ) { - throw new GraphQLError( - 'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.', - ); - } + const runtimeTypeName = + typeof runtimeTypeOrName === 'object' && isNamedType(runtimeTypeOrName) + ? runtimeTypeOrName.name + : runtimeTypeOrName; if (typeof runtimeTypeName !== 'string') { throw new GraphQLError( diff --git a/src/index.ts b/src/index.ts index 83879f00c6..da3ab694f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ export { isEnumType, isInputObjectType, isListType, + isNamedType, isNonNullType, isInputType, isLeafType, diff --git a/src/type/definition.ts b/src/type/definition.ts index 2aa8d8427c..6fd2af4f9b 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -6,6 +6,7 @@ import type { GraphQLInterfaceType, GraphQLLeafType, GraphQLList, + GraphQLNamedType, GraphQLNonNull, GraphQLNullableType, GraphQLObjectType, @@ -148,3 +149,17 @@ function _isWrappingType(type: { [key: string]: any }) { export const isWrappingType = memoize1(_isWrappingType) as (type: { [key: string]: any; }) => type is GraphQLWrappingType; + +function _isNamedType(type: { [key: string]: any }) { + return ( + isScalarType(type) || + isObjectType(type) || + isInterfaceType(type) || + isUnionType(type) || + isEnumType(type) || + isInputObjectType(type) + ); +} +export const isNamedType = memoize1(_isNamedType) as (type: { + [key: string]: any; +}) => type is GraphQLNamedType; diff --git a/src/type/index.ts b/src/type/index.ts index 7d183d7fb9..6c6c099afb 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -14,6 +14,7 @@ export { isEnumType, isInputObjectType, isListType, + isNamedType, isNonNullType, isInputType, isLeafType,