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 #3527
  • Loading branch information
yaacovCR committed Apr 29, 2022
1 parent 5f247e0 commit ed6ddd9
Show file tree
Hide file tree
Showing 41 changed files with 2,455 additions and 50 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
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export {
GraphQLObjectType,
GraphQLInterfaceType,
GraphQLUnionType,
GraphQLIntersectionType,
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLList,
Expand Down Expand Up @@ -90,6 +91,7 @@ export {
isObjectType,
isInterfaceType,
isUnionType,
isIntersectionType,
isEnumType,
isInputObjectType,
isListType,
Expand All @@ -115,6 +117,7 @@ export {
assertObjectType,
assertInterfaceType,
assertUnionType,
assertIntersectionType,
assertEnumType,
assertInputObjectType,
assertListType,
Expand Down Expand Up @@ -181,6 +184,8 @@ export type {
GraphQLInputObjectTypeExtensions,
GraphQLInterfaceTypeConfig,
GraphQLInterfaceTypeExtensions,
GraphQLIntersectionTypeConfig,
GraphQLIntersectionTypeExtensions,
GraphQLIsTypeOfFn,
GraphQLObjectTypeConfig,
GraphQLObjectTypeExtensions,
Expand Down
6 changes: 6 additions & 0 deletions src/language/__tests__/predicates-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('AST node predicates', () => {
'ObjectTypeDefinition',
'InterfaceTypeDefinition',
'UnionTypeDefinition',
'IntersectionTypeDefinition',
'EnumTypeDefinition',
'InputObjectTypeDefinition',
'DirectiveDefinition',
Expand All @@ -42,6 +43,7 @@ describe('AST node predicates', () => {
'ObjectTypeExtension',
'InterfaceTypeExtension',
'UnionTypeExtension',
'IntersectionTypeExtension',
'EnumTypeExtension',
'InputObjectTypeExtension',
]);
Expand Down Expand Up @@ -102,6 +104,7 @@ describe('AST node predicates', () => {
'ObjectTypeDefinition',
'InterfaceTypeDefinition',
'UnionTypeDefinition',
'IntersectionTypeDefinition',
'EnumTypeDefinition',
'InputObjectTypeDefinition',
'DirectiveDefinition',
Expand All @@ -114,6 +117,7 @@ describe('AST node predicates', () => {
'ObjectTypeDefinition',
'InterfaceTypeDefinition',
'UnionTypeDefinition',
'IntersectionTypeDefinition',
'EnumTypeDefinition',
'InputObjectTypeDefinition',
]);
Expand All @@ -126,6 +130,7 @@ describe('AST node predicates', () => {
'ObjectTypeExtension',
'InterfaceTypeExtension',
'UnionTypeExtension',
'IntersectionTypeExtension',
'EnumTypeExtension',
'InputObjectTypeExtension',
]);
Expand All @@ -137,6 +142,7 @@ describe('AST node predicates', () => {
'ObjectTypeExtension',
'InterfaceTypeExtension',
'UnionTypeExtension',
'IntersectionTypeExtension',
'EnumTypeExtension',
'InputObjectTypeExtension',
]);
Expand Down
96 changes: 96 additions & 0 deletions src/language/__tests__/schema-parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ describe('Schema Parser', () => {
locations: [{ line: 1, column: 19 }],
});

expectSyntaxError('extend intersection Hello').to.deep.equal({
message: 'Syntax Error: Unexpected <EOF>.',
locations: [{ line: 1, column: 26 }],
});

expectSyntaxError('extend enum Hello').to.deep.equal({
message: 'Syntax Error: Unexpected <EOF>.',
locations: [{ line: 1, column: 18 }],
Expand Down Expand Up @@ -961,6 +966,97 @@ describe('Schema Parser', () => {
});
});

it('Simple union', () => {
const doc = parse('intersection Hello = World');

expectJSON(doc).toDeepEqual({
kind: 'Document',
definitions: [
{
kind: 'IntersectionTypeDefinition',
name: nameNode('Hello', { start: 13, end: 18 }),
description: undefined,
directives: [],
types: [typeNode('World', { start: 21, end: 26 })],
loc: { start: 0, end: 26 },
},
],
loc: { start: 0, end: 26 },
});
});

it('Intersection with two types', () => {
const doc = parse('intersection Hello = Wo & Rld');

expectJSON(doc).toDeepEqual({
kind: 'Document',
definitions: [
{
kind: 'IntersectionTypeDefinition',
name: nameNode('Hello', { start: 13, end: 18 }),
description: undefined,
directives: [],
types: [
typeNode('Wo', { start: 21, end: 23 }),
typeNode('Rld', { start: 26, end: 29 }),
],
loc: { start: 0, end: 29 },
},
],
loc: { start: 0, end: 29 },
});
});

it('Intersection with two types and leading ampersand', () => {
const doc = parse('intersection Hello = & Wo & Rld');

expectJSON(doc).toDeepEqual({
kind: 'Document',
definitions: [
{
kind: 'IntersectionTypeDefinition',
name: nameNode('Hello', { start: 13, end: 18 }),
description: undefined,
directives: [],
types: [
typeNode('Wo', { start: 23, end: 25 }),
typeNode('Rld', { start: 28, end: 31 }),
],
loc: { start: 0, end: 31 },
},
],
loc: { start: 0, end: 31 },
});
});

it('Intersection fails with no types', () => {
expectSyntaxError('intersection Hello = &').to.deep.equal({
message: 'Syntax Error: Expected Name, found <EOF>.',
locations: [{ line: 1, column: 23 }],
});
});

it('Intersection fails with leading double ampersand', () => {
expectSyntaxError('intersection Hello = && Wo & Rld').to.deep.equal({
message: 'Syntax Error: Expected Name, found "&".',
locations: [{ line: 1, column: 23 }],
});
});

it('Intersection fails with double ampersand', () => {
expectSyntaxError('intersection Hello = Wo && Rld').to.deep.equal({
message: 'Syntax Error: Expected Name, found "&".',
locations: [{ line: 1, column: 26 }],
});
});

it('Intersection fails with trailing ampersand', () => {
expectSyntaxError('intersection Hello = & Wo & Rld &').to.deep.equal({
message: 'Syntax Error: Expected Name, found <EOF>.',
locations: [{ line: 1, column: 34 }],
});
});

it('Scalar', () => {
const doc = parse('scalar Hello');

Expand Down
12 changes: 12 additions & 0 deletions src/language/__tests__/schema-printer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ describe('Printer: SDL document', () => {
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
23 changes: 23 additions & 0 deletions src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export type ASTNode =
| InputValueDefinitionNode
| InterfaceTypeDefinitionNode
| UnionTypeDefinitionNode
| IntersectionTypeDefinitionNode
| EnumTypeDefinitionNode
| EnumValueDefinitionNode
| InputObjectTypeDefinitionNode
Expand All @@ -178,6 +179,7 @@ export type ASTNode =
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| IntersectionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;

Expand Down Expand Up @@ -263,6 +265,7 @@ export const QueryDocumentKeys: {
'fields',
],
UnionTypeDefinition: ['description', 'name', 'directives', 'types'],
IntersectionTypeDefinition: ['description', 'name', 'directives', 'types'],
EnumTypeDefinition: ['description', 'name', 'directives', 'values'],
EnumValueDefinition: ['description', 'name', 'directives'],
InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'],
Expand All @@ -275,6 +278,7 @@ export const QueryDocumentKeys: {
ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'],
InterfaceTypeExtension: ['name', 'interfaces', 'directives', 'fields'],
UnionTypeExtension: ['name', 'directives', 'types'],
IntersectionTypeExtension: ['name', 'directives', 'types'],
EnumTypeExtension: ['name', 'directives', 'values'],
InputObjectTypeExtension: ['name', 'directives', 'fields'],
};
Expand Down Expand Up @@ -568,6 +572,7 @@ export type TypeDefinitionNode =
| ObjectTypeDefinitionNode
| InterfaceTypeDefinitionNode
| UnionTypeDefinitionNode
| IntersectionTypeDefinitionNode
| EnumTypeDefinitionNode
| InputObjectTypeDefinitionNode;

Expand Down Expand Up @@ -628,6 +633,15 @@ export interface UnionTypeDefinitionNode {
readonly types?: ReadonlyArray<NamedTypeNode>;
}

export interface IntersectionTypeDefinitionNode {
readonly kind: Kind.INTERSECTION_TYPE_DEFINITION;
readonly loc?: Location;
readonly description?: StringValueNode;
readonly name: NameNode;
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
readonly types?: ReadonlyArray<NamedTypeNode>;
}

export interface EnumTypeDefinitionNode {
readonly kind: Kind.ENUM_TYPE_DEFINITION;
readonly loc?: Location;
Expand Down Expand Up @@ -684,6 +698,7 @@ export type TypeExtensionNode =
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| IntersectionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;

Expand Down Expand Up @@ -720,6 +735,14 @@ export interface UnionTypeExtensionNode {
readonly types?: ReadonlyArray<NamedTypeNode>;
}

export interface IntersectionTypeExtensionNode {
readonly kind: Kind.INTERSECTION_TYPE_EXTENSION;
readonly loc?: Location;
readonly name: NameNode;
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
readonly types?: ReadonlyArray<NamedTypeNode>;
}

export interface EnumTypeExtensionNode {
readonly kind: Kind.ENUM_TYPE_EXTENSION;
readonly loc?: Location;
Expand Down
1 change: 1 addition & 0 deletions src/language/directiveLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum DirectiveLocation {
ARGUMENT_DEFINITION = 'ARGUMENT_DEFINITION',
INTERFACE = 'INTERFACE',
UNION = 'UNION',
INTERSECTION = 'INTERSECTION',
ENUM = 'ENUM',
ENUM_VALUE = 'ENUM_VALUE',
INPUT_OBJECT = 'INPUT_OBJECT',
Expand Down
2 changes: 2 additions & 0 deletions src/language/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export enum Kind {
INPUT_VALUE_DEFINITION = 'InputValueDefinition',
INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition',
UNION_TYPE_DEFINITION = 'UnionTypeDefinition',
INTERSECTION_TYPE_DEFINITION = 'IntersectionTypeDefinition',
ENUM_TYPE_DEFINITION = 'EnumTypeDefinition',
ENUM_VALUE_DEFINITION = 'EnumValueDefinition',
INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition',
Expand All @@ -64,6 +65,7 @@ export enum Kind {
OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension',
INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension',
UNION_TYPE_EXTENSION = 'UnionTypeExtension',
INTERSECTION_TYPE_EXTENSION = 'IntersectionTypeExtension',
ENUM_TYPE_EXTENSION = 'EnumTypeExtension',
INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension',
}
Expand Down
Loading

0 comments on commit ed6ddd9

Please sign in to comment.