Skip to content

Commit

Permalink
introduce new Intersection type
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yaacovCR committed Apr 29, 2022
1 parent 5ccf579 commit 16e050a
Show file tree
Hide file tree
Showing 42 changed files with 2,679 additions and 56 deletions.
6 changes: 6 additions & 0 deletions docs-old/APIReference-GraphQL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ _Type Definitions_
A union type within GraphQL that defines a list of implementations.
</a>
</li>
<li>
<a href="../type/#graphqlintersectiontype">
<pre>class GraphQLIntersectionType</pre>
An intersection type within GraphQL that defines a list of constraining types.
</a>
</li>
<li>
<a href="../type/#graphqlenumtype">
<pre>class GraphQLEnumType</pre>
Expand Down
6 changes: 6 additions & 0 deletions docs-old/APIReference-TypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ _Definitions_
A union type within GraphQL that defines a list of implementations.
</a>
</li>
<li>
<a href="#graphqlintersectiontype">
<pre>class GraphQLIntersectionType</pre>
An intersection type within GraphQL that defines a list of constraining types.
</a>
</li>
<li>
<a href="#graphqlenumtype">
<pre>class GraphQLEnumType</pre>
Expand Down
12 changes: 12 additions & 0 deletions src/__testUtils__/kitchenSinkSDL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ extend union Feed = Photo | Video
extend union Feed @onUnion
intersection Resource = Feed & Node
intersection AnnotatedIntersection @onIntersection = Feed & Node
intersection AnnotatedIntersectionTwo @onIntersection = Feed & Node
intersection UndefinedIntersection
extend intersection Resource = Media & Accessible
extend intersection Resource @onIntersection
scalar CustomScalar
scalar AnnotatedScalar @onScalar
Expand Down
230 changes: 224 additions & 6 deletions src/execution/__tests__/union-interface-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { parse } from '../../language/parser';

import {
GraphQLInterfaceType,
GraphQLIntersectionType,
GraphQLList,
GraphQLObjectType,
GraphQLUnionType,
Expand Down Expand Up @@ -45,15 +46,18 @@ class Cat {
class Person {
name: string;
pets?: ReadonlyArray<Dog | Cat>;
friends?: ReadonlyArray<Dog | Cat | Person>;
mammalPets?: ReadonlyArray<Dog | Cat>;
friends?: ReadonlyArray<Dog | Person>;

constructor(
name: string,
pets?: ReadonlyArray<Dog | Cat>,
mammalPets?: ReadonlyArray<Dog | Cat>,
friends?: ReadonlyArray<Dog | Cat | Person>,
) {
this.name = name;
this.pets = pets;
this.mammalPets = mammalPets;
this.friends = friends;
}
}
Expand Down Expand Up @@ -130,6 +134,7 @@ const PersonType: GraphQLObjectType = new GraphQLObjectType({
fields: () => ({
name: { type: GraphQLString },
pets: { type: new GraphQLList(PetType) },
mammalPets: { type: new GraphQLList(MammalPetType) },
friends: { type: new GraphQLList(NamedType) },
progeny: { type: new GraphQLList(PersonType) },
mother: { type: PersonType },
Expand All @@ -138,6 +143,22 @@ const PersonType: GraphQLObjectType = new GraphQLObjectType({
isTypeOf: (value) => value instanceof Person,
});

const MammalPetType = new GraphQLIntersectionType({
name: 'MammalPet',
types: [PetType, MammalType],
resolveType(value) {
if (value instanceof Dog) {
return DogType.name;
}
if (value instanceof Cat) {
return CatType.name;
}
/* c8 ignore next 3 */
// Not reachable, all possible types have been considered.
expect.fail('Not reachable');
},
});

const schema = new GraphQLSchema({
query: PersonType,
types: [PetType],
Expand All @@ -152,17 +173,23 @@ odie.mother = new Dog("Odie's Mom", true);
odie.mother.progeny = [odie];

const liz = new Person('Liz');
const john = new Person('John', [garfield, odie], [liz, odie]);

describe('Execute: Union and intersection types', () => {
it('can introspect on union and intersection types', () => {
const john = new Person(
'John',
[garfield, odie],
[garfield, odie],
[liz, odie],
);

describe('Execute: Union, interface and intersection types', () => {
it('can introspect on union, interface and intersection types', () => {
const document = parse(`
{
Named: __type(name: "Named") {
kind
name
fields { name }
interfaces { name }
memberTypes { name }
possibleTypes { name }
enumValues { name }
inputFields { name }
Expand All @@ -172,6 +199,7 @@ describe('Execute: Union and intersection types', () => {
name
fields { name }
interfaces { name }
memberTypes { name }
possibleTypes { name }
enumValues { name }
inputFields { name }
Expand All @@ -181,6 +209,17 @@ describe('Execute: Union and intersection types', () => {
name
fields { name }
interfaces { name }
memberTypes { name }
possibleTypes { name }
enumValues { name }
inputFields { name }
}
MammalPet: __type(name: "MammalPet") {
kind
name
fields { name }
interfaces { name }
memberTypes { name }
possibleTypes { name }
enumValues { name }
inputFields { name }
Expand All @@ -195,6 +234,7 @@ describe('Execute: Union and intersection types', () => {
name: 'Named',
fields: [{ name: 'name' }],
interfaces: [],
memberTypes: null,
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }, { name: 'Person' }],
enumValues: null,
inputFields: null,
Expand All @@ -204,6 +244,7 @@ describe('Execute: Union and intersection types', () => {
name: 'Mammal',
fields: [{ name: 'progeny' }, { name: 'mother' }, { name: 'father' }],
interfaces: [{ name: 'Life' }],
memberTypes: null,
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }, { name: 'Person' }],
enumValues: null,
inputFields: null,
Expand All @@ -213,6 +254,17 @@ describe('Execute: Union and intersection types', () => {
name: 'Pet',
fields: null,
interfaces: null,
memberTypes: null,
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }],
enumValues: null,
inputFields: null,
},
MammalPet: {
kind: 'INTERSECTION',
name: 'MammalPet',
fields: null,
interfaces: null,
memberTypes: [{ name: 'Pet' }, { name: 'Mammal' }],
possibleTypes: [{ name: 'Dog' }, { name: 'Cat' }],
enumValues: null,
inputFields: null,
Expand Down Expand Up @@ -418,12 +470,152 @@ describe('Execute: Union and intersection types', () => {
});
});

it('executes using intersection types', () => {
// NOTE: This is an *invalid* query, but it should be an *executable* query.
const document = parse(`
{
__typename
name
mammalPets {
__typename
name
barks
meows
}
}
`);

expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
data: {
__typename: 'Person',
name: 'John',
mammalPets: [
{ __typename: 'Cat', name: 'Garfield', meows: false },
{ __typename: 'Dog', name: 'Odie', barks: true },
],
},
});
});

it('executes intersection types with inline fragments', () => {
// This is the valid version of the query in the above test.
const document = parse(`
{
__typename
name
mammalPets {
__typename
name
... on Dog {
barks
}
... on Cat {
meows
}
... on Mammal {
mother {
__typename
... on Dog {
name
barks
}
... on Cat {
name
meows
}
}
}
}
}
`);

expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
data: {
__typename: 'Person',
name: 'John',
mammalPets: [
{
__typename: 'Cat',
name: 'Garfield',
meows: false,
mother: {
__typename: 'Cat',
name: "Garfield's Mom",
meows: false,
},
},
{
__typename: 'Dog',
name: 'Odie',
barks: true,
mother: {
__typename: 'Dog',
name: "Odie's Mom",
barks: true,
},
},
],
},
});
});

it('executes intersection types with named fragments', () => {
const document = parse(`
{
__typename
name
mammalPets {
__typename
name
...DogBarks
...CatMeows
}
}
fragment DogBarks on Dog {
barks
}
fragment CatMeows on Cat {
meows
}
`);

expect(executeSync({ schema, document, rootValue: john })).to.deep.equal({
data: {
__typename: 'Person',
name: 'John',
mammalPets: [
{
__typename: 'Cat',
name: 'Garfield',
meows: false,
},
{
__typename: 'Dog',
name: 'Odie',
barks: true,
},
],
},
});
});

it('allows fragment conditions to be abstract types', () => {
const document = parse(`
{
__typename
name
pets {
...MammalPetFields,
...on Mammal {
mother {
...ProgenyFields
}
}
}
mammalPets {
...PetFields,
...on Mammal {
mother {
Expand All @@ -434,6 +626,18 @@ describe('Execute: Union and intersection types', () => {
friends { ...FriendFields }
}
fragment MammalPetFields on Pet {
__typename
... on Dog {
name
barks
}
... on Cat {
name
meows
}
}
fragment PetFields on Pet {
__typename
... on Dog {
Expand Down Expand Up @@ -482,6 +686,20 @@ describe('Execute: Union and intersection types', () => {
mother: { progeny: [{ __typename: 'Dog' }] },
},
],
mammalPets: [
{
__typename: 'Cat',
name: 'Garfield',
meows: false,
mother: { progeny: [{ __typename: 'Cat' }] },
},
{
__typename: 'Dog',
name: 'Odie',
barks: true,
mother: { progeny: [{ __typename: 'Dog' }] },
},
],
friends: [
{
__typename: 'Person',
Expand Down Expand Up @@ -525,7 +743,7 @@ describe('Execute: Union and intersection types', () => {
});
const schema2 = new GraphQLSchema({ query: PersonType2 });
const document = parse('{ name, friends { name } }');
const rootValue = new Person('John', [], [liz]);
const rootValue = new Person('John', [], [], [liz]);
const contextValue = { authToken: '123abc' };

const result = executeSync({
Expand Down
Loading

0 comments on commit 16e050a

Please sign in to comment.