Skip to content

Commit 74d5712

Browse files
committed
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
1 parent 7532a5c commit 74d5712

File tree

2 files changed

+275
-37
lines changed

2 files changed

+275
-37
lines changed

src/execution/__tests__/abstract-test.ts

+173-6
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ describe('Execute: Handles execution of abstract types', () => {
271271
errors: [
272272
{
273273
message:
274-
'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.',
274+
'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.',
275275
locations: [{ line: 3, column: 9 }],
276276
path: ['pet'],
277277
},
@@ -610,26 +610,26 @@ describe('Execute: Handles execution of abstract types', () => {
610610
}
611611

612612
expectError({ forTypeName: undefined }).toEqual(
613-
'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.',
613+
'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.',
614614
);
615615

616616
expectError({ forTypeName: 'Human' }).toEqual(
617-
'Abstract type "Pet" was resolved to a type "Human" that does not exist inside the schema.',
617+
'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.',
618618
);
619619

620620
expectError({ forTypeName: 'String' }).toEqual(
621-
'Abstract type "Pet" was resolved to a non-object type "String".',
621+
'Abstract type resolution for "Pet" for field "Query.pet" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".',
622622
);
623623

624624
expectError({ forTypeName: '__Schema' }).toEqual(
625-
'Runtime Object type "__Schema" is not a possible type for "Pet".',
625+
'Abstract type resolution for "Pet" for field "Query.pet" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".',
626626
);
627627

628628
// FIXME: workaround since we can't inject resolveType into SDL
629629
// @ts-expect-error
630630
assertInterfaceType(schema.getType('Pet')).resolveType = () => [];
631631
expectError({ forTypeName: undefined }).toEqual(
632-
'Abstract type "Pet" must resolve to an Object type at runtime for field "Query.pet" with value { __typename: undefined }, received "[]".',
632+
'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 "[]".',
633633
);
634634

635635
// FIXME: workaround since we can't inject resolveType into SDL
@@ -640,4 +640,171 @@ describe('Execute: Handles execution of abstract types', () => {
640640
'Support for returning GraphQLObjectType from resolveType was removed in [email protected] please return type name instead.',
641641
);
642642
});
643+
644+
it('hierarchical resolveType with Interfaces yields useful error', () => {
645+
const schema = buildSchema(`
646+
type Query {
647+
named: Named
648+
}
649+
650+
interface Named {
651+
name: String
652+
}
653+
654+
interface Animal {
655+
isFriendly: Boolean
656+
}
657+
658+
interface Pet implements Named & Animal {
659+
name: String
660+
isFriendly: Boolean
661+
}
662+
663+
type Cat implements Pet & Named & Animal {
664+
name: String
665+
isFriendly: Boolean
666+
}
667+
668+
type Dog implements Pet & Named & Animal {
669+
name: String
670+
isFriendly: Boolean
671+
}
672+
673+
type Person implements Named {
674+
name: String
675+
}
676+
`);
677+
678+
const document = parse(`
679+
{
680+
named {
681+
name
682+
}
683+
}
684+
`);
685+
686+
function expectError() {
687+
const rootValue = { named: {} };
688+
const result = executeSync({ schema, document, rootValue });
689+
return {
690+
toEqual(message: string) {
691+
expectJSON(result).toDeepEqual({
692+
data: { named: null },
693+
errors: [
694+
{
695+
message,
696+
locations: [{ line: 3, column: 9 }],
697+
path: ['named'],
698+
},
699+
],
700+
});
701+
},
702+
};
703+
}
704+
705+
const namedType = assertInterfaceType(schema.getType('Named'));
706+
// FIXME: workaround since we can't inject resolveType into SDL
707+
namedType.resolveType = () => 'Animal';
708+
expectError().toEqual(
709+
'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Animal" is not a subtype of encountered interface type "Named".',
710+
);
711+
712+
const petType = assertInterfaceType(schema.getType('Pet'));
713+
// FIXME: workaround since we can't inject resolveType into SDL
714+
namedType.resolveType = () => 'Pet';
715+
petType.resolveType = () => 'Person';
716+
expectError().toEqual(
717+
'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "Person" is not a possible type for encountered abstract type "Pet".',
718+
);
719+
720+
// FIXME: workaround since we can't inject resolveType into SDL
721+
namedType.resolveType = () => 'Pet';
722+
petType.resolveType = () => undefined;
723+
expectError().toEqual(
724+
'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.',
725+
);
726+
727+
// FIXME: workaround since we can't inject resolveType into SDL
728+
petType.resolveType = () => 'Human';
729+
expectError().toEqual(
730+
'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.',
731+
);
732+
733+
// FIXME: workaround since we can't inject resolveType into SDL
734+
petType.resolveType = () => 'String';
735+
expectError().toEqual(
736+
'Abstract type resolution for "Named" for field "Query.named" failed. Encountered abstract type "Pet" was resolved to a non-object type "String".',
737+
);
738+
739+
// FIXME: workaround since we can't inject resolveType into SDL
740+
petType.resolveType = () => '__Schema';
741+
expectError().toEqual(
742+
'Abstract type resolution for "Named" for field "Query.named" failed. Runtime Object type "__Schema" is not a possible type for encountered abstract type "Pet".',
743+
);
744+
745+
// FIXME: workaround since we can't inject resolveType into SDL
746+
// @ts-expect-error
747+
petType.resolveType = () => [];
748+
expectError().toEqual(
749+
'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 "[]".',
750+
);
751+
752+
// FIXME: workaround since we can't inject resolveType into SDL
753+
petType.resolveType = () => 'Pet';
754+
expectError().toEqual(
755+
'Abstract type resolution for "Named" for field "Query.named" failed. Interface type "Pet" is not a subtype of encountered interface type "Named".',
756+
);
757+
});
758+
759+
it('cyclical resolveType with (unvalidated) cyclical Interface aborts with error', () => {
760+
const schema = buildSchema(
761+
`
762+
type Query {
763+
test: FooInterface
764+
}
765+
766+
interface FooInterface implements FooInterface {
767+
field: String
768+
}
769+
`,
770+
{ assumeValid: true },
771+
);
772+
773+
const document = parse(`
774+
{
775+
test {
776+
field
777+
}
778+
}
779+
`);
780+
781+
function expectError() {
782+
const rootValue = { test: {} };
783+
const result = executeSync({ schema, document, rootValue });
784+
return {
785+
toEqual(message: string) {
786+
expectJSON(result).toDeepEqual({
787+
data: { test: null },
788+
errors: [
789+
{
790+
message,
791+
locations: [{ line: 3, column: 9 }],
792+
path: ['test'],
793+
},
794+
],
795+
});
796+
},
797+
};
798+
}
799+
800+
const fooInterfaceType = assertInterfaceType(
801+
schema.getType('FooInterface'),
802+
);
803+
804+
// FIXME: workaround since we can't inject resolveType into SDL
805+
fooInterfaceType.resolveType = () => 'FooInterface';
806+
expectError().toEqual(
807+
'Abstract type resolution for "FooInterface" for field "Query.test" failed. Encountered abstract type "FooInterface" resolved to "FooInterface", causing a cycle.',
808+
);
809+
});
643810
});

0 commit comments

Comments
 (0)