Skip to content

Commit

Permalink
Allow interface resolveType functions to resolve to child interfaces
Browse files Browse the repository at this point in the history
= The child interfaces must eventually resolve to a runtime object type.
= Interface cycles raise a runtime error.

implements: #3253
  • Loading branch information
yaacovCR committed Jun 1, 2022
1 parent 7532a5c commit 0d7cdd3
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 37 deletions.
179 changes: 173 additions & 6 deletions src/execution/__tests__/abstract-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
Expand Down Expand Up @@ -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
Expand All @@ -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.',
);
});
});
Loading

0 comments on commit 0d7cdd3

Please sign in to comment.