From a30daec3d9faaba3b88764d61425ed1bfaf7e48d Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Fri, 20 May 2022 00:07:07 +0300 Subject: [PATCH 01/10] Allow interface resolveType functions to resolve to child interfaces = The child interfaces must eventually resolve to a runtime object type. = Interface cycles raise a runtime error. --- src/execution/__tests__/abstract-test.ts | 114 +++++++++++++++++++-- src/execution/execute.ts | 125 ++++++++++++++++++----- 2 files changed, 207 insertions(+), 32 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 5253d0d9e0..b4db7c2720 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -271,7 +271,7 @@ describe('Execute: Handles execution of abstract types', () => { errors: [ { message: - 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet". Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', locations: [{ line: 3, column: 9 }], path: ['pet'], }, @@ -610,26 +610,26 @@ 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". Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', ); expectError({ forTypeName: 'Human' }).toEqual( - 'Abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', + 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', ); expectError({ forTypeName: 'String' }).toEqual( - 'Abstract type "Pet" was resolved to a non-object type "String".', + 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".', ); expectError({ forTypeName: '__Schema' }).toEqual( - 'Runtime Object type "__Schema" is not a possible type for "Pet".', + 'Abstract type resolution for "Pet" for field "Query.pet" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".', ); // FIXME: workaround since we can't inject resolveType into SDL // @ts-expect-error assertInterfaceType(schema.getType('Pet')).resolveType = () => []; expectError({ forTypeName: undefined }).toEqual( - 'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".', + 'Abstract type resolution for "Pet" for field "Query.pet" with value { __typename: undefined } failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime, received "[]".', ); // FIXME: workaround since we can't inject resolveType into SDL @@ -640,4 +640,106 @@ describe('Execute: Handles execution of abstract types', () => { 'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.', ); }); + + it('hierarchical resolveType with Interfaces yields useful error', () => { + const schema = buildSchema(` + type Query { + named: Named + } + + interface Named { + name: String + } + + interface Animal { + isFriendly: Boolean + } + + interface Pet implements Animal & Named { + name: String + isFriendly: Boolean + } + + type Cat implements Pet & Named & Animal { + name: String + isFriendly: Boolean + } + + type Dog implements Pet & Animal & Named { + name: String + isFriendly: Boolean + } + `); + + const document = parse(` + { + named { + name + } + } + `); + + function expectError() { + const rootValue = { named: {} }; + const result = executeSync({ schema, document, rootValue }); + return { + toEqual(message: string) { + expectJSON(result).toDeepEqual({ + data: { named: null }, + errors: [ + { + message, + locations: [{ line: 3, column: 9 }], + path: ['named'], + }, + ], + }); + }, + }; + } + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Named')).resolveType = () => 'Animal'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Animal" is not a subtype of encountered interface type "Named".', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Named')).resolveType = () => 'Pet'; + assertInterfaceType(schema.getType('Pet')).resolveType = () => undefined; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Pet')).resolveType = () => 'Human'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Pet')).resolveType = () => 'String'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Pet')).resolveType = () => '__Schema'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + // @ts-expect-error + assertInterfaceType(schema.getType('Pet')).resolveType = () => []; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" with value {} failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime, received "[]".', + ); + + // FIXME: workaround since we can't inject resolveType into SDL + assertInterfaceType(schema.getType('Pet')).resolveType = () => 'Pet'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" resolved to "Pet", causing a cycle.', + ); + }); }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 6dc4246178..c3a5576cac 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -39,6 +39,7 @@ import type { } from '../type/definition'; import { isAbstractType, + isInterfaceType, isLeafType, isListType, isNonNullType, @@ -796,22 +797,20 @@ function completeAbstractValue( path: Path, result: unknown, ): PromiseOrValue> { - const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver; - const contextValue = exeContext.contextValue; - const runtimeType = resolveTypeFn(result, contextValue, info, returnType); + const runtimeType = resolveType( + exeContext, + returnType, + returnType, + fieldNodes, + info, + result, + ); if (isPromise(runtimeType)) { return runtimeType.then((resolvedRuntimeType) => completeObjectValue( exeContext, - ensureValidRuntimeType( - resolvedRuntimeType, - exeContext, - returnType, - fieldNodes, - info, - result, - ), + resolvedRuntimeType, fieldNodes, info, path, @@ -822,14 +821,7 @@ function completeAbstractValue( return completeObjectValue( exeContext, - ensureValidRuntimeType( - runtimeType, - exeContext, - returnType, - fieldNodes, - info, - result, - ), + runtimeType, fieldNodes, info, path, @@ -837,17 +829,65 @@ function completeAbstractValue( ); } +function resolveType( + exeContext: ExecutionContext, + returnType: GraphQLAbstractType, + abstractType: GraphQLAbstractType, + fieldNodes: ReadonlyArray, + info: GraphQLResolveInfo, + result: unknown, + encounteredTypeNames: Set = new Set(), +): GraphQLObjectType | Promise { + const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; + const contextValue = exeContext.contextValue; + const possibleRuntimeType = resolveTypeFn( + result, + contextValue, + info, + abstractType, + ); + + if (isPromise(possibleRuntimeType)) { + return possibleRuntimeType.then((resolvedPossibleRuntimeType) => + ensureValidRuntimeType( + resolvedPossibleRuntimeType, + exeContext, + returnType, + abstractType, + fieldNodes, + info, + result, + encounteredTypeNames, + ), + ); + } + return ensureValidRuntimeType( + possibleRuntimeType, + exeContext, + returnType, + abstractType, + fieldNodes, + info, + result, + encounteredTypeNames, + ); +} + function ensureValidRuntimeType( runtimeTypeName: unknown, exeContext: ExecutionContext, returnType: GraphQLAbstractType, + abstractType: GraphQLAbstractType, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, result: unknown, -): GraphQLObjectType { + encounteredTypeNames: Set, +): GraphQLObjectType | Promise { if (runtimeTypeName == 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.`, + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Encountered abstract type "${abstractType.name}" must resolve to an Object or Interface type at runtime. ` + + `Either the "${abstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, { nodes: fieldNodes }, ); } @@ -862,29 +902,62 @@ function ensureValidRuntimeType( if (typeof runtimeTypeName !== 'string') { throw new GraphQLError( - `Abstract type "${returnType.name}" must resolve to an Object type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` + - `value ${inspect(result)}, received "${inspect(runtimeTypeName)}".`, + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" ` + + `with value ${inspect(result)} failed. ` + + `Encountered abstract type "${abstractType.name}" must resolve to an Object or Interface type at runtime, ` + + `received "${inspect(runtimeTypeName)}".`, + ); + } + + if (encounteredTypeNames.has(runtimeTypeName)) { + throw new GraphQLError( + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Encountered abstract type "${abstractType.name}" resolved to "${runtimeTypeName}", causing a cycle.`, ); } + encounteredTypeNames.add(runtimeTypeName); const runtimeType = exeContext.schema.getType(runtimeTypeName); if (runtimeType == null) { throw new GraphQLError( - `Abstract type "${returnType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Encountered abstract type "${abstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, { nodes: fieldNodes }, ); } + if (isInterfaceType(runtimeType)) { + if (!exeContext.schema.isSubType(returnType, runtimeType)) { + throw new GraphQLError( + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Interface type "${runtimeType.name}" is not a subtype of encountered interface type "${returnType.name}".`, + { nodes: fieldNodes }, + ); + } + + return resolveType( + exeContext, + returnType, + runtimeType, + fieldNodes, + info, + result, + encounteredTypeNames, + ); + } + if (!isObjectType(runtimeType)) { throw new GraphQLError( - `Abstract type "${returnType.name}" was resolved to a non-object type "${runtimeTypeName}".`, + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Encountered abstract type "${abstractType.name}" was resolved to a non-object type "${runtimeTypeName}".`, { nodes: fieldNodes }, ); } if (!exeContext.schema.isSubType(returnType, runtimeType)) { throw new GraphQLError( - `Runtime Object type "${runtimeType.name}" is not a possible type for "${returnType.name}".`, + `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + + `Runtime Object type "${runtimeType.name}" is not a possible type for encountered abstract type "${abstractType.name}".`, { nodes: fieldNodes }, ); } From d89b1ae3d0d0f41863c8bc975726b5e8b43e3924 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 22 May 2022 23:14:05 +0300 Subject: [PATCH 02/10] change function name --- src/execution/execute.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/execution/execute.ts b/src/execution/execute.ts index c3a5576cac..fa1ecdaf68 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -840,17 +840,17 @@ function resolveType( ): GraphQLObjectType | Promise { const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; const contextValue = exeContext.contextValue; - const possibleRuntimeType = resolveTypeFn( + const possibleRuntimeTypeName = resolveTypeFn( result, contextValue, info, abstractType, ); - if (isPromise(possibleRuntimeType)) { - return possibleRuntimeType.then((resolvedPossibleRuntimeType) => - ensureValidRuntimeType( - resolvedPossibleRuntimeType, + if (isPromise(possibleRuntimeTypeName)) { + return possibleRuntimeTypeName.then((resolvedPossibleRuntimeTypeName) => + deriveRuntimeType( + resolvedPossibleRuntimeTypeName, exeContext, returnType, abstractType, @@ -861,8 +861,8 @@ function resolveType( ), ); } - return ensureValidRuntimeType( - possibleRuntimeType, + return deriveRuntimeType( + possibleRuntimeTypeName, exeContext, returnType, abstractType, @@ -873,7 +873,7 @@ function resolveType( ); } -function ensureValidRuntimeType( +function deriveRuntimeType( runtimeTypeName: unknown, exeContext: ExecutionContext, returnType: GraphQLAbstractType, From 00dbea1a6f279da1704cdf540effbb3c7bc9b7b1 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 22 May 2022 23:28:46 +0300 Subject: [PATCH 03/10] use variables for abstract types in tests --- src/execution/__tests__/abstract-test.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index b4db7c2720..37432742e6 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -698,46 +698,48 @@ describe('Execute: Handles execution of abstract types', () => { }; } + const namedType = assertInterfaceType(schema.getType('Named')); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Named')).resolveType = () => 'Animal'; + namedType.resolveType = () => 'Animal'; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Animal" is not a subtype of encountered interface type "Named".', ); + const petType = assertInterfaceType(schema.getType('Pet')); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Named')).resolveType = () => 'Pet'; - assertInterfaceType(schema.getType('Pet')).resolveType = () => undefined; + namedType.resolveType = () => 'Pet'; + petType.resolveType = () => undefined; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', ); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Pet')).resolveType = () => 'Human'; + petType.resolveType = () => 'Human'; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', ); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Pet')).resolveType = () => 'String'; + petType.resolveType = () => 'String'; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".', ); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Pet')).resolveType = () => '__Schema'; + petType.resolveType = () => '__Schema'; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".', ); // FIXME: workaround since we can't inject resolveType into SDL // @ts-expect-error - assertInterfaceType(schema.getType('Pet')).resolveType = () => []; + petType.resolveType = () => []; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" with value {} failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime, received "[]".', ); // FIXME: workaround since we can't inject resolveType into SDL - assertInterfaceType(schema.getType('Pet')).resolveType = () => 'Pet'; + petType.resolveType = () => 'Pet'; expectError().toEqual( 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" resolved to "Pet", causing a cycle.', ); From 7167fcf686e33034e7ea9f95c443998f6551d206 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 22 May 2022 23:33:02 +0300 Subject: [PATCH 04/10] fix chain and add test --- src/execution/__tests__/abstract-test.ts | 11 +++++++++++ src/execution/execute.ts | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 37432742e6..3031589f79 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -669,6 +669,10 @@ describe('Execute: Handles execution of abstract types', () => { name: String isFriendly: Boolean } + + type Person implements Named { + name: String + } `); const document = parse(` @@ -706,6 +710,13 @@ describe('Execute: Handles execution of abstract types', () => { ); const petType = assertInterfaceType(schema.getType('Pet')); + // FIXME: workaround since we can't inject resolveType into SDL + namedType.resolveType = () => 'Pet'; + petType.resolveType = () => 'Person'; + expectError().toEqual( + 'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "Person" is not a possible type for encountered abstract type "Pet".', + ); + // FIXME: workaround since we can't inject resolveType into SDL namedType.resolveType = () => 'Pet'; petType.resolveType = () => undefined; diff --git a/src/execution/execute.ts b/src/execution/execute.ts index fa1ecdaf68..ff47a0a6b1 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -877,7 +877,7 @@ function deriveRuntimeType( runtimeTypeName: unknown, exeContext: ExecutionContext, returnType: GraphQLAbstractType, - abstractType: GraphQLAbstractType, + currentAbstractType: GraphQLAbstractType, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, result: unknown, @@ -886,8 +886,8 @@ function deriveRuntimeType( if (runtimeTypeName == null) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${abstractType.name}" must resolve to an Object or Interface type at runtime. ` + - `Either the "${abstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, + `Encountered abstract type "${currentAbstractType.name}" must resolve to an Object or Interface type at runtime. ` + + `Either the "${currentAbstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, { nodes: fieldNodes }, ); } @@ -904,7 +904,7 @@ function deriveRuntimeType( throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" ` + `with value ${inspect(result)} failed. ` + - `Encountered abstract type "${abstractType.name}" must resolve to an Object or Interface type at runtime, ` + + `Encountered abstract type "${currentAbstractType.name}" must resolve to an Object or Interface type at runtime, ` + `received "${inspect(runtimeTypeName)}".`, ); } @@ -912,7 +912,7 @@ function deriveRuntimeType( if (encounteredTypeNames.has(runtimeTypeName)) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${abstractType.name}" resolved to "${runtimeTypeName}", causing a cycle.`, + `Encountered abstract type "${currentAbstractType.name}" resolved to "${runtimeTypeName}", causing a cycle.`, ); } encounteredTypeNames.add(runtimeTypeName); @@ -921,13 +921,13 @@ function deriveRuntimeType( if (runtimeType == null) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${abstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, + `Encountered abstract type "${currentAbstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, { nodes: fieldNodes }, ); } if (isInterfaceType(runtimeType)) { - if (!exeContext.schema.isSubType(returnType, runtimeType)) { + if (!exeContext.schema.isSubType(currentAbstractType, runtimeType)) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + `Interface type "${runtimeType.name}" is not a subtype of encountered interface type "${returnType.name}".`, @@ -949,15 +949,15 @@ function deriveRuntimeType( if (!isObjectType(runtimeType)) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${abstractType.name}" was resolved to a non-object type "${runtimeTypeName}".`, + `Encountered abstract type "${currentAbstractType.name}" was resolved to a non-object type "${runtimeTypeName}".`, { nodes: fieldNodes }, ); } - if (!exeContext.schema.isSubType(returnType, runtimeType)) { + if (!exeContext.schema.isSubType(currentAbstractType, runtimeType)) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Runtime Object type "${runtimeType.name}" is not a possible type for encountered abstract type "${abstractType.name}".`, + `Runtime Object type "${runtimeType.name}" is not a possible type for encountered abstract type "${currentAbstractType.name}".`, { nodes: fieldNodes }, ); } From 81c5ed97ff858f736c182ffa498291e63307a304 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 22 May 2022 23:38:24 +0300 Subject: [PATCH 05/10] standardize order --- src/execution/__tests__/abstract-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 3031589f79..026acb65cb 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -655,7 +655,7 @@ describe('Execute: Handles execution of abstract types', () => { isFriendly: Boolean } - interface Pet implements Animal & Named { + interface Pet implements Named & Animal { name: String isFriendly: Boolean } @@ -665,7 +665,7 @@ describe('Execute: Handles execution of abstract types', () => { isFriendly: Boolean } - type Dog implements Pet & Animal & Named { + type Dog implements Pet & Named & Animal { name: String isFriendly: Boolean } From 6c8ad45e53371b24b8d39e33a36877c5e82a7321 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Mon, 23 May 2022 20:12:29 +0300 Subject: [PATCH 06/10] remove unneeded check for cycles --- src/execution/__tests__/abstract-test.ts | 2 +- src/execution/execute.ts | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 026acb65cb..782b0118ec 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -752,7 +752,7 @@ describe('Execute: Handles execution of abstract types', () => { // FIXME: workaround since we can't inject resolveType into SDL petType.resolveType = () => 'Pet'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" resolved to "Pet", causing a cycle.', + 'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Pet" is not a subtype of encountered interface type "Named".', ); }); }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index ff47a0a6b1..e2ee7a10d9 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -836,7 +836,6 @@ function resolveType( fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, result: unknown, - encounteredTypeNames: Set = new Set(), ): GraphQLObjectType | Promise { const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; const contextValue = exeContext.contextValue; @@ -857,7 +856,6 @@ function resolveType( fieldNodes, info, result, - encounteredTypeNames, ), ); } @@ -869,7 +867,6 @@ function resolveType( fieldNodes, info, result, - encounteredTypeNames, ); } @@ -881,7 +878,6 @@ function deriveRuntimeType( fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, result: unknown, - encounteredTypeNames: Set, ): GraphQLObjectType | Promise { if (runtimeTypeName == null) { throw new GraphQLError( @@ -909,14 +905,6 @@ function deriveRuntimeType( ); } - if (encounteredTypeNames.has(runtimeTypeName)) { - throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${currentAbstractType.name}" resolved to "${runtimeTypeName}", causing a cycle.`, - ); - } - encounteredTypeNames.add(runtimeTypeName); - const runtimeType = exeContext.schema.getType(runtimeTypeName); if (runtimeType == null) { throw new GraphQLError( @@ -942,7 +930,6 @@ function deriveRuntimeType( fieldNodes, info, result, - encounteredTypeNames, ); } From 4dbfe20e1477bdcea0d209620d35c1db11415c1c Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Wed, 1 Jun 2022 21:54:22 +0300 Subject: [PATCH 07/10] review change --- src/execution/execute.ts | 93 ++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 60 deletions(-) diff --git a/src/execution/execute.ts b/src/execution/execute.ts index e2ee7a10d9..17315f6f71 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -668,6 +668,7 @@ function completeValue( return completeAbstractValue( exeContext, returnType, + returnType, fieldNodes, info, path, @@ -792,36 +793,26 @@ function completeLeafValue( function completeAbstractValue( exeContext: ExecutionContext, returnType: GraphQLAbstractType, + abstractType: GraphQLAbstractType, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, path: Path, result: unknown, ): PromiseOrValue> { - const runtimeType = resolveType( - exeContext, - returnType, - returnType, - fieldNodes, - info, + const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; + const contextValue = exeContext.contextValue; + const runtimeTypeName = resolveTypeFn( result, + contextValue, + info, + abstractType, ); - if (isPromise(runtimeType)) { - return runtimeType.then((resolvedRuntimeType) => - completeObjectValue( - exeContext, - resolvedRuntimeType, - fieldNodes, - info, - path, - result, - ), - ); - } - - return completeObjectValue( + return resolveRuntimeTypeName( exeContext, - runtimeType, + returnType, + abstractType, + runtimeTypeName, fieldNodes, info, path, @@ -829,56 +820,31 @@ function completeAbstractValue( ); } -function resolveType( +function resolveRuntimeTypeName( exeContext: ExecutionContext, returnType: GraphQLAbstractType, - abstractType: GraphQLAbstractType, + currentAbstractType: GraphQLAbstractType, + runtimeTypeName: PromiseOrValue, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, + path: Path, result: unknown, -): GraphQLObjectType | Promise { - const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; - const contextValue = exeContext.contextValue; - const possibleRuntimeTypeName = resolveTypeFn( - result, - contextValue, - info, - abstractType, - ); - - if (isPromise(possibleRuntimeTypeName)) { - return possibleRuntimeTypeName.then((resolvedPossibleRuntimeTypeName) => - deriveRuntimeType( - resolvedPossibleRuntimeTypeName, +): PromiseOrValue> { + if (isPromise(runtimeTypeName)) { + return runtimeTypeName.then((resolved) => + resolveRuntimeTypeName( exeContext, returnType, - abstractType, + currentAbstractType, + resolved, fieldNodes, info, + path, result, ), ); } - return deriveRuntimeType( - possibleRuntimeTypeName, - exeContext, - returnType, - abstractType, - fieldNodes, - info, - result, - ); -} -function deriveRuntimeType( - runtimeTypeName: unknown, - exeContext: ExecutionContext, - returnType: GraphQLAbstractType, - currentAbstractType: GraphQLAbstractType, - fieldNodes: ReadonlyArray, - info: GraphQLResolveInfo, - result: unknown, -): GraphQLObjectType | Promise { if (runtimeTypeName == null) { throw new GraphQLError( `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + @@ -922,13 +888,13 @@ function deriveRuntimeType( { nodes: fieldNodes }, ); } - - return resolveType( + return completeAbstractValue( exeContext, returnType, runtimeType, fieldNodes, info, + path, result, ); } @@ -949,7 +915,14 @@ function deriveRuntimeType( ); } - return runtimeType; + return completeObjectValue( + exeContext, + runtimeType, + fieldNodes, + info, + path, + result, + ); } /** From 5ba43d83b40bfe6a8c460a4e22ab1dfd675a7b52 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 2 Jun 2022 21:29:27 +0300 Subject: [PATCH 08/10] change inner recursive function to completeAbstractValueImpl --- src/execution/execute.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 17315f6f71..6a00526dc0 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -808,7 +808,7 @@ function completeAbstractValue( abstractType, ); - return resolveRuntimeTypeName( + return completeAbstractValueImpl( exeContext, returnType, abstractType, @@ -820,7 +820,7 @@ function completeAbstractValue( ); } -function resolveRuntimeTypeName( +function completeAbstractValueImpl( exeContext: ExecutionContext, returnType: GraphQLAbstractType, currentAbstractType: GraphQLAbstractType, @@ -832,7 +832,7 @@ function resolveRuntimeTypeName( ): PromiseOrValue> { if (isPromise(runtimeTypeName)) { return runtimeTypeName.then((resolved) => - resolveRuntimeTypeName( + completeAbstractValueImpl( exeContext, returnType, currentAbstractType, From 9e0c4d084d0ce02e1176b4b950338a8be7a0e36e Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 2 Jun 2022 22:08:41 +0300 Subject: [PATCH 09/10] re-align error message as possible to original --- src/execution/__tests__/abstract-test.ts | 28 ++++++++--------- src/execution/execute.ts | 38 ++++++++---------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/execution/__tests__/abstract-test.ts b/src/execution/__tests__/abstract-test.ts index 782b0118ec..8a6c95e043 100644 --- a/src/execution/__tests__/abstract-test.ts +++ b/src/execution/__tests__/abstract-test.ts @@ -271,7 +271,7 @@ describe('Execute: Handles execution of abstract types', () => { errors: [ { message: - 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + 'Abstract type "Pet" must resolve to an Object type or an intermediate Interface type at runtime for field "Query.pet". Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', locations: [{ line: 3, column: 9 }], path: ['pet'], }, @@ -610,26 +610,26 @@ describe('Execute: Handles execution of abstract types', () => { } expectError({ forTypeName: undefined }).toEqual( - 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + 'Abstract type "Pet" must resolve to an Object type or an intermediate Interface type at runtime for field "Query.pet". Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', ); expectError({ forTypeName: 'Human' }).toEqual( - 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', + 'Abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', ); expectError({ forTypeName: 'String' }).toEqual( - 'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".', + 'Abstract type "Pet" was resolved to a non-object and non-interface type "String".', ); expectError({ forTypeName: '__Schema' }).toEqual( - 'Abstract type resolution for "Pet" for field "Query.pet" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".', + 'Runtime Object type "__Schema" is not a possible type for "Pet".', ); // FIXME: workaround since we can't inject resolveType into SDL // @ts-expect-error assertInterfaceType(schema.getType('Pet')).resolveType = () => []; expectError({ forTypeName: undefined }).toEqual( - 'Abstract type resolution for "Pet" for field "Query.pet" with value { __typename: undefined } failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime, received "[]".', + 'Abstract type "Pet" must resolve to an Object type or an intermediate Interface type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".', ); // FIXME: workaround since we can't inject resolveType into SDL @@ -706,7 +706,7 @@ describe('Execute: Handles execution of abstract types', () => { // FIXME: workaround since we can't inject resolveType into SDL namedType.resolveType = () => 'Animal'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Animal" is not a subtype of encountered interface type "Named".', + 'Interface type "Animal" is not a possible type for "Named".', ); const petType = assertInterfaceType(schema.getType('Pet')); @@ -714,45 +714,45 @@ describe('Execute: Handles execution of abstract types', () => { namedType.resolveType = () => 'Pet'; petType.resolveType = () => 'Person'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "Person" is not a possible type for encountered abstract type "Pet".', + 'Runtime Object type "Person" is not a possible type for "Pet".', ); // FIXME: workaround since we can't inject resolveType into SDL namedType.resolveType = () => 'Pet'; petType.resolveType = () => undefined; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime. Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', + 'Abstract type "Pet" must resolve to an Object type or an intermediate Interface type at runtime for field "Query.named". Either the "Pet" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.', ); // FIXME: workaround since we can't inject resolveType into SDL petType.resolveType = () => 'Human'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', + 'Abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.', ); // FIXME: workaround since we can't inject resolveType into SDL petType.resolveType = () => 'String'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".', + 'Abstract type "Pet" was resolved to a non-object and non-interface type "String".', ); // FIXME: workaround since we can't inject resolveType into SDL petType.resolveType = () => '__Schema'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".', + 'Runtime Object type "__Schema" is not a possible type for "Pet".', ); // FIXME: workaround since we can't inject resolveType into SDL // @ts-expect-error petType.resolveType = () => []; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" with value {} failed. Encountered abstract type "Pet" must resolve to an Object or Interface type at runtime, received "[]".', + 'Abstract type "Pet" must resolve to an Object type or an intermediate Interface type at runtime for field "Query.named" with value {}, received "[]".', ); // FIXME: workaround since we can't inject resolveType into SDL petType.resolveType = () => 'Pet'; expectError().toEqual( - 'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Pet" is not a subtype of encountered interface type "Named".', + 'Interface type "Pet" is not a possible type for "Pet".', ); }); }); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 6a00526dc0..f8e4964fd4 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -668,7 +668,6 @@ function completeValue( return completeAbstractValue( exeContext, returnType, - returnType, fieldNodes, info, path, @@ -792,7 +791,6 @@ function completeLeafValue( */ function completeAbstractValue( exeContext: ExecutionContext, - returnType: GraphQLAbstractType, abstractType: GraphQLAbstractType, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, @@ -810,7 +808,6 @@ function completeAbstractValue( return completeAbstractValueImpl( exeContext, - returnType, abstractType, runtimeTypeName, fieldNodes, @@ -822,8 +819,7 @@ function completeAbstractValue( function completeAbstractValueImpl( exeContext: ExecutionContext, - returnType: GraphQLAbstractType, - currentAbstractType: GraphQLAbstractType, + abstractType: GraphQLAbstractType, runtimeTypeName: PromiseOrValue, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, @@ -834,8 +830,7 @@ function completeAbstractValueImpl( return runtimeTypeName.then((resolved) => completeAbstractValueImpl( exeContext, - returnType, - currentAbstractType, + abstractType, resolved, fieldNodes, info, @@ -847,9 +842,7 @@ function completeAbstractValueImpl( if (runtimeTypeName == null) { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${currentAbstractType.name}" must resolve to an Object or Interface type at runtime. ` + - `Either the "${currentAbstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, + `Abstract type "${abstractType.name}" must resolve to an Object type or an intermediate Interface type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${abstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, { nodes: fieldNodes }, ); } @@ -864,33 +857,29 @@ function completeAbstractValueImpl( if (typeof runtimeTypeName !== 'string') { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" ` + - `with value ${inspect(result)} failed. ` + - `Encountered abstract type "${currentAbstractType.name}" must resolve to an Object or Interface type at runtime, ` + - `received "${inspect(runtimeTypeName)}".`, + `Abstract type "${abstractType.name}" must resolve to an Object type or an intermediate Interface type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` + + `value ${inspect(result)}, received "${inspect(runtimeTypeName)}".`, ); } const runtimeType = exeContext.schema.getType(runtimeTypeName); if (runtimeType == null) { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${currentAbstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, + `Abstract type "${abstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, { nodes: fieldNodes }, ); } if (isInterfaceType(runtimeType)) { - if (!exeContext.schema.isSubType(currentAbstractType, runtimeType)) { + if (!exeContext.schema.isSubType(abstractType, runtimeType)) { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Interface type "${runtimeType.name}" is not a subtype of encountered interface type "${returnType.name}".`, + `Interface type "${runtimeType.name}" is not a possible type for "${abstractType.name}".`, { nodes: fieldNodes }, ); } + return completeAbstractValue( exeContext, - returnType, runtimeType, fieldNodes, info, @@ -901,20 +890,17 @@ function completeAbstractValueImpl( if (!isObjectType(runtimeType)) { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Encountered abstract type "${currentAbstractType.name}" was resolved to a non-object type "${runtimeTypeName}".`, + `Abstract type "${abstractType.name}" was resolved to a non-object and non-interface type "${runtimeTypeName}".`, { nodes: fieldNodes }, ); } - if (!exeContext.schema.isSubType(currentAbstractType, runtimeType)) { + if (!exeContext.schema.isSubType(abstractType, runtimeType)) { throw new GraphQLError( - `Abstract type resolution for "${returnType.name}" for field "${info.parentType.name}.${info.fieldName}" failed. ` + - `Runtime Object type "${runtimeType.name}" is not a possible type for encountered abstract type "${currentAbstractType.name}".`, + `Runtime Object type "${runtimeType.name}" is not a possible type for "${abstractType.name}".`, { nodes: fieldNodes }, ); } - return completeObjectValue( exeContext, runtimeType, From 225cab91bbddf54db933046be569b6f6dc710d4b Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 2 Jun 2022 22:19:40 +0300 Subject: [PATCH 10/10] rename runtimeType(Name) to possibleRuntimeType(Name) because it could not be the runtime type(name), could be an intermediate interface I considered just renaming it to resolvedTypeName, but that's confusing, because the variable could hold a promise, and resolvedResolvedTypeName overloads "resolve" --- src/execution/execute.ts | 46 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/execution/execute.ts b/src/execution/execute.ts index f8e4964fd4..8f6c3ae12c 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -799,7 +799,7 @@ function completeAbstractValue( ): PromiseOrValue> { const resolveTypeFn = abstractType.resolveType ?? exeContext.typeResolver; const contextValue = exeContext.contextValue; - const runtimeTypeName = resolveTypeFn( + const possibleRuntimeTypeName = resolveTypeFn( result, contextValue, info, @@ -809,7 +809,7 @@ function completeAbstractValue( return completeAbstractValueImpl( exeContext, abstractType, - runtimeTypeName, + possibleRuntimeTypeName, fieldNodes, info, path, @@ -820,14 +820,14 @@ function completeAbstractValue( function completeAbstractValueImpl( exeContext: ExecutionContext, abstractType: GraphQLAbstractType, - runtimeTypeName: PromiseOrValue, + possibleRuntimeTypeName: PromiseOrValue, fieldNodes: ReadonlyArray, info: GraphQLResolveInfo, path: Path, result: unknown, ): PromiseOrValue> { - if (isPromise(runtimeTypeName)) { - return runtimeTypeName.then((resolved) => + if (isPromise(possibleRuntimeTypeName)) { + return possibleRuntimeTypeName.then((resolved) => completeAbstractValueImpl( exeContext, abstractType, @@ -840,7 +840,7 @@ function completeAbstractValueImpl( ); } - if (runtimeTypeName == null) { + if (possibleRuntimeTypeName == null) { throw new GraphQLError( `Abstract type "${abstractType.name}" must resolve to an Object type or an intermediate Interface type at runtime for field "${info.parentType.name}.${info.fieldName}". Either the "${abstractType.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`, { nodes: fieldNodes }, @@ -849,38 +849,42 @@ function completeAbstractValueImpl( // releases before 16.0.0 supported returning `GraphQLObjectType` from `resolveType` // TODO: remove in 17.0.0 release - if (isObjectType(runtimeTypeName)) { + if (isObjectType(possibleRuntimeTypeName)) { throw new GraphQLError( 'Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.', ); } - if (typeof runtimeTypeName !== 'string') { + if (typeof possibleRuntimeTypeName !== 'string') { throw new GraphQLError( `Abstract type "${abstractType.name}" must resolve to an Object type or an intermediate Interface type at runtime for field "${info.parentType.name}.${info.fieldName}" with ` + - `value ${inspect(result)}, received "${inspect(runtimeTypeName)}".`, + `value ${inspect(result)}, received "${inspect( + possibleRuntimeTypeName, + )}".`, ); } - const runtimeType = exeContext.schema.getType(runtimeTypeName); - if (runtimeType == null) { + const possibleRuntimeType = exeContext.schema.getType( + possibleRuntimeTypeName, + ); + if (possibleRuntimeType == null) { throw new GraphQLError( - `Abstract type "${abstractType.name}" was resolved to a type "${runtimeTypeName}" that does not exist inside the schema.`, + `Abstract type "${abstractType.name}" was resolved to a type "${possibleRuntimeTypeName}" that does not exist inside the schema.`, { nodes: fieldNodes }, ); } - if (isInterfaceType(runtimeType)) { - if (!exeContext.schema.isSubType(abstractType, runtimeType)) { + if (isInterfaceType(possibleRuntimeType)) { + if (!exeContext.schema.isSubType(abstractType, possibleRuntimeType)) { throw new GraphQLError( - `Interface type "${runtimeType.name}" is not a possible type for "${abstractType.name}".`, + `Interface type "${possibleRuntimeType.name}" is not a possible type for "${abstractType.name}".`, { nodes: fieldNodes }, ); } return completeAbstractValue( exeContext, - runtimeType, + possibleRuntimeType, fieldNodes, info, path, @@ -888,22 +892,22 @@ function completeAbstractValueImpl( ); } - if (!isObjectType(runtimeType)) { + if (!isObjectType(possibleRuntimeType)) { throw new GraphQLError( - `Abstract type "${abstractType.name}" was resolved to a non-object and non-interface type "${runtimeTypeName}".`, + `Abstract type "${abstractType.name}" was resolved to a non-object and non-interface type "${possibleRuntimeTypeName}".`, { nodes: fieldNodes }, ); } - if (!exeContext.schema.isSubType(abstractType, runtimeType)) { + if (!exeContext.schema.isSubType(abstractType, possibleRuntimeType)) { throw new GraphQLError( - `Runtime Object type "${runtimeType.name}" is not a possible type for "${abstractType.name}".`, + `Runtime Object type "${possibleRuntimeType.name}" is not a possible type for "${abstractType.name}".`, { nodes: fieldNodes }, ); } return completeObjectValue( exeContext, - runtimeType, + possibleRuntimeType, fieldNodes, info, path,