-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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. implements: #3253
- Loading branch information
Showing
2 changed files
with
275 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,171 @@ describe('Execute: Handles execution of abstract types', () => { | |
'Support for returning GraphQLObjectType from resolveType was removed in [email protected] 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 Named & Animal { | ||
name: String | ||
isFriendly: Boolean | ||
} | ||
type Cat implements Pet & Named & Animal { | ||
name: String | ||
isFriendly: Boolean | ||
} | ||
type Dog implements Pet & Named & Animal { | ||
name: String | ||
isFriendly: Boolean | ||
} | ||
type Person implements Named { | ||
name: String | ||
} | ||
`); | ||
|
||
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'], | ||
}, | ||
], | ||
}); | ||
}, | ||
}; | ||
} | ||
|
||
const namedType = assertInterfaceType(schema.getType('Named')); | ||
// 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".', | ||
); | ||
|
||
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; | ||
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 | ||
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 | ||
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 | ||
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 | ||
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 | ||
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".', | ||
); | ||
}); | ||
|
||
it('cyclical resolveType with (unvalidated) cyclical Interface aborts with error', () => { | ||
const schema = buildSchema( | ||
` | ||
type Query { | ||
test: FooInterface | ||
} | ||
interface FooInterface implements FooInterface { | ||
field: String | ||
} | ||
`, | ||
{ assumeValid: true }, | ||
); | ||
|
||
const document = parse(` | ||
{ | ||
test { | ||
field | ||
} | ||
} | ||
`); | ||
|
||
function expectError() { | ||
const rootValue = { test: {} }; | ||
const result = executeSync({ schema, document, rootValue }); | ||
return { | ||
toEqual(message: string) { | ||
expectJSON(result).toDeepEqual({ | ||
data: { test: null }, | ||
errors: [ | ||
{ | ||
message, | ||
locations: [{ line: 3, column: 9 }], | ||
path: ['test'], | ||
}, | ||
], | ||
}); | ||
}, | ||
}; | ||
} | ||
|
||
const fooInterfaceType = assertInterfaceType( | ||
schema.getType('FooInterface'), | ||
); | ||
|
||
// FIXME: workaround since we can't inject resolveType into SDL | ||
fooInterfaceType.resolveType = () => 'FooInterface'; | ||
expectError().toEqual( | ||
'Abstract type resolution for "FooInterface" for field "Query.test" failed. Encountered abstract type "FooInterface" resolved to "FooInterface", causing a cycle.', | ||
); | ||
}); | ||
}); |
Oops, something went wrong.