Skip to content

Commit 16e050a

Browse files
committed
introduce new Intersection type
Intersections of unions and interfaces can be considered to "implement" their unions and interface members. A type with a field of type Intersection will satisfy an interface where the field defined in the interface is one of the member types of the intersection. Alternative to graphql#3527
1 parent 5ccf579 commit 16e050a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2679
-56
lines changed

docs-old/APIReference-GraphQL.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ _Type Definitions_
6666
A union type within GraphQL that defines a list of implementations.
6767
</a>
6868
</li>
69+
<li>
70+
<a href="../type/#graphqlintersectiontype">
71+
<pre>class GraphQLIntersectionType</pre>
72+
An intersection type within GraphQL that defines a list of constraining types.
73+
</a>
74+
</li>
6975
<li>
7076
<a href="../type/#graphqlenumtype">
7177
<pre>class GraphQLEnumType</pre>

docs-old/APIReference-TypeSystem.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ _Definitions_
5454
A union type within GraphQL that defines a list of implementations.
5555
</a>
5656
</li>
57+
<li>
58+
<a href="#graphqlintersectiontype">
59+
<pre>class GraphQLIntersectionType</pre>
60+
An intersection type within GraphQL that defines a list of constraining types.
61+
</a>
62+
</li>
5763
<li>
5864
<a href="#graphqlenumtype">
5965
<pre>class GraphQLEnumType</pre>

src/__testUtils__/kitchenSinkSDL.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ extend union Feed = Photo | Video
7979
8080
extend union Feed @onUnion
8181
82+
intersection Resource = Feed & Node
83+
84+
intersection AnnotatedIntersection @onIntersection = Feed & Node
85+
86+
intersection AnnotatedIntersectionTwo @onIntersection = Feed & Node
87+
88+
intersection UndefinedIntersection
89+
90+
extend intersection Resource = Media & Accessible
91+
92+
extend intersection Resource @onIntersection
93+
8294
scalar CustomScalar
8395
8496
scalar AnnotatedScalar @onScalar

src/execution/__tests__/union-interface-test.ts

Lines changed: 224 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { parse } from '../../language/parser';
55

66
import {
77
GraphQLInterfaceType,
8+
GraphQLIntersectionType,
89
GraphQLList,
910
GraphQLObjectType,
1011
GraphQLUnionType,
@@ -45,15 +46,18 @@ class Cat {
4546
class Person {
4647
name: string;
4748
pets?: ReadonlyArray<Dog | Cat>;
48-
friends?: ReadonlyArray<Dog | Cat | Person>;
49+
mammalPets?: ReadonlyArray<Dog | Cat>;
50+
friends?: ReadonlyArray<Dog | Person>;
4951

5052
constructor(
5153
name: string,
5254
pets?: ReadonlyArray<Dog | Cat>,
55+
mammalPets?: ReadonlyArray<Dog | Cat>,
5356
friends?: ReadonlyArray<Dog | Cat | Person>,
5457
) {
5558
this.name = name;
5659
this.pets = pets;
60+
this.mammalPets = mammalPets;
5761
this.friends = friends;
5862
}
5963
}
@@ -130,6 +134,7 @@ const PersonType: GraphQLObjectType = new GraphQLObjectType({
130134
fields: () => ({
131135
name: { type: GraphQLString },
132136
pets: { type: new GraphQLList(PetType) },
137+
mammalPets: { type: new GraphQLList(MammalPetType) },
133138
friends: { type: new GraphQLList(NamedType) },
134139
progeny: { type: new GraphQLList(PersonType) },
135140
mother: { type: PersonType },
@@ -138,6 +143,22 @@ const PersonType: GraphQLObjectType = new GraphQLObjectType({
138143
isTypeOf: (value) => value instanceof Person,
139144
});
140145

146+
const MammalPetType = new GraphQLIntersectionType({
147+
name: 'MammalPet',
148+
types: [PetType, MammalType],
149+
resolveType(value) {
150+
if (value instanceof Dog) {
151+
return DogType.name;
152+
}
153+
if (value instanceof Cat) {
154+
return CatType.name;
155+
}
156+
/* c8 ignore next 3 */
157+
// Not reachable, all possible types have been considered.
158+
expect.fail('Not reachable');
159+
},
160+
});
161+
141162
const schema = new GraphQLSchema({
142163
query: PersonType,
143164
types: [PetType],
@@ -152,17 +173,23 @@ odie.mother = new Dog("Odie's Mom", true);
152173
odie.mother.progeny = [odie];
153174

154175
const liz = new Person('Liz');
155-
const john = new Person('John', [garfield, odie], [liz, odie]);
156-
157-
describe('Execute: Union and intersection types', () => {
158-
it('can introspect on union and intersection types', () => {
176+
const john = new Person(
177+
'John',
178+
[garfield, odie],
179+
[garfield, odie],
180+
[liz, odie],
181+
);
182+
183+
describe('Execute: Union, interface and intersection types', () => {
184+
it('can introspect on union, interface and intersection types', () => {
159185
const document = parse(`
160186
{
161187
Named: __type(name: "Named") {
162188
kind
163189
name
164190
fields { name }
165191
interfaces { name }
192+
memberTypes { name }
166193
possibleTypes { name }
167194
enumValues { name }
168195
inputFields { name }
@@ -172,6 +199,7 @@ describe('Execute: Union and intersection types', () => {
172199
name
173200
fields { name }
174201
interfaces { name }
202+
memberTypes { name }
175203
possibleTypes { name }
176204
enumValues { name }
177205
inputFields { name }
@@ -181,6 +209,17 @@ describe('Execute: Union and intersection types', () => {
181209
name
182210
fields { name }
183211
interfaces { name }
212+
memberTypes { name }
213+
possibleTypes { name }
214+
enumValues { name }
215+
inputFields { name }
216+
}
217+
MammalPet: __type(name: "MammalPet") {
218+
kind
219+
name
220+
fields { name }
221+
interfaces { name }
222+
memberTypes { name }
184223
possibleTypes { name }
185224
enumValues { name }
186225
inputFields { name }
@@ -195,6 +234,7 @@ describe('Execute: Union and intersection types', () => {
195234
name: 'Named',
196235
fields: [{ name: 'name' }],
197236
interfaces: [],
237+
memberTypes: null,
198238
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }, { name: 'Person' }],
199239
enumValues: null,
200240
inputFields: null,
@@ -204,6 +244,7 @@ describe('Execute: Union and intersection types', () => {
204244
name: 'Mammal',
205245
fields: [{ name: 'progeny' }, { name: 'mother' }, { name: 'father' }],
206246
interfaces: [{ name: 'Life' }],
247+
memberTypes: null,
207248
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }, { name: 'Person' }],
208249
enumValues: null,
209250
inputFields: null,
@@ -213,6 +254,17 @@ describe('Execute: Union and intersection types', () => {
213254
name: 'Pet',
214255
fields: null,
215256
interfaces: null,
257+
memberTypes: null,
258+
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }],
259+
enumValues: null,
260+
inputFields: null,
261+
},
262+
MammalPet: {
263+
kind: 'INTERSECTION',
264+
name: 'MammalPet',
265+
fields: null,
266+
interfaces: null,
267+
memberTypes: [{ name: 'Pet' }, { name: 'Mammal' }],
216268
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }],
217269
enumValues: null,
218270
inputFields: null,
@@ -418,12 +470,152 @@ describe('Execute: Union and intersection types', () => {
418470
});
419471
});
420472

473+
it('executes using intersection types', () => {
474+
// NOTE: This is an *invalid* query, but it should be an *executable* query.
475+
const document = parse(`
476+
{
477+
__typename
478+
name
479+
mammalPets {
480+
__typename
481+
name
482+
barks
483+
meows
484+
}
485+
}
486+
`);
487+
488+
expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
489+
data: {
490+
__typename: 'Person',
491+
name: 'John',
492+
mammalPets: [
493+
{ __typename: 'Cat', name: 'Garfield', meows: false },
494+
{ __typename: 'Dog', name: 'Odie', barks: true },
495+
],
496+
},
497+
});
498+
});
499+
500+
it('executes intersection types with inline fragments', () => {
501+
// This is the valid version of the query in the above test.
502+
const document = parse(`
503+
{
504+
__typename
505+
name
506+
mammalPets {
507+
__typename
508+
name
509+
... on Dog {
510+
barks
511+
}
512+
... on Cat {
513+
meows
514+
}
515+
516+
... on Mammal {
517+
mother {
518+
__typename
519+
... on Dog {
520+
name
521+
barks
522+
}
523+
... on Cat {
524+
name
525+
meows
526+
}
527+
}
528+
}
529+
}
530+
}
531+
`);
532+
533+
expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
534+
data: {
535+
__typename: 'Person',
536+
name: 'John',
537+
mammalPets: [
538+
{
539+
__typename: 'Cat',
540+
name: 'Garfield',
541+
meows: false,
542+
mother: {
543+
__typename: 'Cat',
544+
name: "Garfield's Mom",
545+
meows: false,
546+
},
547+
},
548+
{
549+
__typename: 'Dog',
550+
name: 'Odie',
551+
barks: true,
552+
mother: {
553+
__typename: 'Dog',
554+
name: "Odie's Mom",
555+
barks: true,
556+
},
557+
},
558+
],
559+
},
560+
});
561+
});
562+
563+
it('executes intersection types with named fragments', () => {
564+
const document = parse(`
565+
{
566+
__typename
567+
name
568+
mammalPets {
569+
__typename
570+
name
571+
...DogBarks
572+
...CatMeows
573+
}
574+
}
575+
576+
fragment DogBarks on Dog {
577+
barks
578+
}
579+
580+
fragment CatMeows on Cat {
581+
meows
582+
}
583+
`);
584+
585+
expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
586+
data: {
587+
__typename: 'Person',
588+
name: 'John',
589+
mammalPets: [
590+
{
591+
__typename: 'Cat',
592+
name: 'Garfield',
593+
meows: false,
594+
},
595+
{
596+
__typename: 'Dog',
597+
name: 'Odie',
598+
barks: true,
599+
},
600+
],
601+
},
602+
});
603+
});
604+
421605
it('allows fragment conditions to be abstract types', () => {
422606
const document = parse(`
423607
{
424608
__typename
425609
name
426610
pets {
611+
...MammalPetFields,
612+
...on Mammal {
613+
mother {
614+
...ProgenyFields
615+
}
616+
}
617+
}
618+
mammalPets {
427619
...PetFields,
428620
...on Mammal {
429621
mother {
@@ -434,6 +626,18 @@ describe('Execute: Union and intersection types', () => {
434626
friends { ...FriendFields }
435627
}
436628
629+
fragment MammalPetFields on Pet {
630+
__typename
631+
... on Dog {
632+
name
633+
barks
634+
}
635+
... on Cat {
636+
name
637+
meows
638+
}
639+
}
640+
437641
fragment PetFields on Pet {
438642
__typename
439643
... on Dog {
@@ -482,6 +686,20 @@ describe('Execute: Union and intersection types', () => {
482686
mother: { progeny: [{ __typename: 'Dog' }] },
483687
},
484688
],
689+
mammalPets: [
690+
{
691+
__typename: 'Cat',
692+
name: 'Garfield',
693+
meows: false,
694+
mother: { progeny: [{ __typename: 'Cat' }] },
695+
},
696+
{
697+
__typename: 'Dog',
698+
name: 'Odie',
699+
barks: true,
700+
mother: { progeny: [{ __typename: 'Dog' }] },
701+
},
702+
],
485703
friends: [
486704
{
487705
__typename: 'Person',
@@ -525,7 +743,7 @@ describe('Execute: Union and intersection types', () => {
525743
});
526744
const schema2 = new GraphQLSchema({ query: PersonType2 });
527745
const document = parse('{ name, friends { name } }');
528-
const rootValue = new Person('John', [], [liz]);
746+
const rootValue = new Person('John', [], [], [liz]);
529747
const contextValue = { authToken: '123abc' };
530748

531749
const result = executeSync({

0 commit comments

Comments
 (0)