Skip to content

Commit cae9794

Browse files
committed
buildSchema/extendSchema: add support for extensions
Fixes #922
1 parent c0cf320 commit cae9794

File tree

3 files changed

+163
-20
lines changed

3 files changed

+163
-20
lines changed

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535

3636
import { graphqlSync } from '../../graphql';
3737

38-
import { printSchema } from '../schemaPrinter';
38+
import { printType, printSchema } from '../schemaPrinter';
3939
import { buildASTSchema, buildSchema } from '../buildASTSchema';
4040

4141
/**
@@ -55,6 +55,17 @@ function printASTNode(obj) {
5555
return print(obj.astNode);
5656
}
5757

58+
function printAllASTNodes(obj) {
59+
invariant(obj != null);
60+
invariant(obj.astNode != null);
61+
invariant(obj.extensionASTNodes != null);
62+
63+
return print({
64+
kind: Kind.DOCUMENT,
65+
definitions: [obj.astNode, ...obj.extensionASTNodes],
66+
});
67+
}
68+
5869
describe('Schema Builder', () => {
5970
it('can use built schema for limited execution', () => {
6071
const schema = buildASTSchema(
@@ -737,6 +748,89 @@ describe('Schema Builder', () => {
737748
});
738749
});
739750

751+
it('Correctly extend scalar type', () => {
752+
const scalarSDL = dedent`
753+
scalar SomeScalar
754+
755+
extend scalar SomeScalar @foo
756+
757+
extend scalar SomeScalar @bar
758+
`;
759+
const schema = buildSchema(`
760+
${scalarSDL}
761+
directive @foo on SCALAR
762+
directive @bar on SCALAR
763+
`);
764+
765+
const someScalar = assertScalarType(schema.getType('SomeScalar'));
766+
expect(printType(someScalar) + '\n').to.equal(dedent`
767+
scalar SomeScalar
768+
`);
769+
770+
expect(printAllASTNodes(someScalar)).to.equal(scalarSDL);
771+
});
772+
773+
it('Correctly extend object type', () => {
774+
const objectSDL = dedent`
775+
type SomeObject implements Foo {
776+
first: String
777+
}
778+
779+
extend type SomeObject implements Bar {
780+
second: Int
781+
}
782+
783+
extend type SomeObject implements Baz {
784+
third: Float
785+
}
786+
`;
787+
const schema = buildSchema(`
788+
${objectSDL}
789+
interface Foo
790+
interface Bar
791+
interface Baz
792+
`);
793+
794+
const someObject = assertObjectType(schema.getType('SomeObject'));
795+
expect(printType(someObject) + '\n').to.equal(dedent`
796+
type SomeObject implements Foo & Bar & Baz {
797+
first: String
798+
second: Int
799+
third: Float
800+
}
801+
`);
802+
803+
expect(printAllASTNodes(someObject)).to.equal(objectSDL);
804+
});
805+
806+
it('Correctly extend interface type', () => {
807+
const interfaceSDL = dedent`
808+
interface SomeInterface {
809+
first: String
810+
}
811+
812+
extend interface SomeInterface {
813+
second: Int
814+
}
815+
816+
extend interface SomeInterface {
817+
third: Float
818+
}
819+
`;
820+
const schema = buildSchema(interfaceSDL);
821+
822+
const someInterface = assertInterfaceType(schema.getType('SomeInterface'));
823+
expect(printType(someInterface) + '\n').to.equal(dedent`
824+
interface SomeInterface {
825+
first: String
826+
second: Int
827+
third: Float
828+
}
829+
`);
830+
831+
expect(printAllASTNodes(someInterface)).to.equal(interfaceSDL);
832+
});
833+
740834
it('Correctly assign AST nodes', () => {
741835
const sdl = dedent`
742836
schema {

src/utilities/buildASTSchema.js

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,24 @@ import keyMap from '../jsutils/keyMap';
66
import inspect from '../jsutils/inspect';
77
import invariant from '../jsutils/invariant';
88
import devAssert from '../jsutils/devAssert';
9-
import { type ObjMap } from '../jsutils/ObjMap';
9+
import { type ObjMap, type ReadOnlyObjMap } from '../jsutils/ObjMap';
1010

1111
import { Kind } from '../language/kinds';
1212
import { type Source } from '../language/source';
1313
import { TokenKind } from '../language/tokenKind';
1414
import { type ParseOptions, parse } from '../language/parser';
15-
import { isTypeDefinitionNode } from '../language/predicates';
1615
import { dedentBlockStringValue } from '../language/blockString';
1716
import { type DirectiveLocationEnum } from '../language/directiveLocation';
17+
import {
18+
isTypeDefinitionNode,
19+
isTypeExtensionNode,
20+
} from '../language/predicates';
1821
import {
1922
type Location,
2023
type StringValueNode,
2124
type DocumentNode,
2225
type TypeNode,
26+
type TypeExtensionNode,
2327
type NamedTypeNode,
2428
type SchemaDefinitionNode,
2529
type SchemaExtensionNode,
@@ -127,15 +131,26 @@ export function buildASTSchema(
127131
assertValidSDL(documentAST);
128132
}
129133

134+
// Collect the definitions and extensions found in the document.
130135
let schemaDef: ?SchemaDefinitionNode;
136+
const schemaExts: Array<SchemaExtensionNode> = [];
131137
const typeDefs: Array<TypeDefinitionNode> = [];
138+
const typeExtsMap: ObjMap<Array<TypeExtensionNode>> = Object.create(null);
132139
const directiveDefs: Array<DirectiveDefinitionNode> = [];
133140

134141
for (const def of documentAST.definitions) {
135142
if (def.kind === Kind.SCHEMA_DEFINITION) {
136143
schemaDef = def;
144+
} else if (def.kind === Kind.SCHEMA_EXTENSION) {
145+
schemaExts.push(def);
137146
} else if (isTypeDefinitionNode(def)) {
138147
typeDefs.push(def);
148+
} else if (isTypeExtensionNode(def)) {
149+
const extendedTypeName = def.name.value;
150+
const existingTypeExts = typeExtsMap[extendedTypeName];
151+
typeExtsMap[extendedTypeName] = existingTypeExts
152+
? existingTypeExts.concat([def])
153+
: [def];
139154
} else if (def.kind === Kind.DIRECTIVE_DEFINITION) {
140155
directiveDefs.push(def);
141156
}
@@ -149,7 +164,7 @@ export function buildASTSchema(
149164
return type;
150165
});
151166

152-
const typeMap = astBuilder.buildTypeMap(typeDefs);
167+
const typeMap = astBuilder.buildTypeMap(typeDefs, typeExtsMap);
153168
const operationTypes = schemaDef
154169
? astBuilder.getOperationTypes([schemaDef])
155170
: {
@@ -394,63 +409,97 @@ export class ASTDefinitionBuilder {
394409

395410
buildTypeMap(
396411
nodes: $ReadOnlyArray<TypeDefinitionNode>,
412+
extensionMap: ReadOnlyObjMap<$ReadOnlyArray<TypeExtensionNode>>,
397413
): ObjMap<GraphQLNamedType> {
398414
const typeMap = Object.create(null);
399415
for (const node of nodes) {
400416
const name = node.name.value;
401-
typeMap[name] = stdTypeMap[name] || this._buildType(node);
417+
typeMap[name] =
418+
stdTypeMap[name] || this._buildType(node, extensionMap[name] || []);
402419
}
403420
return typeMap;
404421
}
405422

406-
_buildType(astNode: TypeDefinitionNode): GraphQLNamedType {
423+
_buildType(
424+
astNode: TypeDefinitionNode,
425+
extensionNodes: $ReadOnlyArray<TypeExtensionNode>,
426+
): GraphQLNamedType {
407427
const name = astNode.name.value;
408428
const description = getDescription(astNode, this._options);
409429

410430
switch (astNode.kind) {
411-
case Kind.OBJECT_TYPE_DEFINITION:
431+
case Kind.OBJECT_TYPE_DEFINITION: {
432+
const extensionASTNodes = (extensionNodes: any);
433+
const allNodes = [astNode, ...extensionASTNodes];
434+
412435
return new GraphQLObjectType({
413436
name,
414437
description,
415-
interfaces: () => this.buildInterfaces([astNode]),
416-
fields: () => this.buildFieldMap([astNode]),
438+
interfaces: () => this.buildInterfaces(allNodes),
439+
fields: () => this.buildFieldMap(allNodes),
417440
astNode,
441+
extensionASTNodes,
418442
});
419-
case Kind.INTERFACE_TYPE_DEFINITION:
443+
}
444+
case Kind.INTERFACE_TYPE_DEFINITION: {
445+
const extensionASTNodes = (extensionNodes: any);
446+
const allNodes = [astNode, ...extensionASTNodes];
447+
420448
return new GraphQLInterfaceType({
421449
name,
422450
description,
423-
interfaces: () => this.buildInterfaces([astNode]),
424-
fields: () => this.buildFieldMap([astNode]),
451+
interfaces: () => this.buildInterfaces(allNodes),
452+
fields: () => this.buildFieldMap(allNodes),
425453
astNode,
454+
extensionASTNodes,
426455
});
427-
case Kind.ENUM_TYPE_DEFINITION:
456+
}
457+
case Kind.ENUM_TYPE_DEFINITION: {
458+
const extensionASTNodes = (extensionNodes: any);
459+
const allNodes = [astNode, ...extensionASTNodes];
460+
428461
return new GraphQLEnumType({
429462
name,
430463
description,
431-
values: this.buildEnumValueMap([astNode]),
464+
values: this.buildEnumValueMap(allNodes),
432465
astNode,
466+
extensionASTNodes,
433467
});
434-
case Kind.UNION_TYPE_DEFINITION:
468+
}
469+
case Kind.UNION_TYPE_DEFINITION: {
470+
const extensionASTNodes = (extensionNodes: any);
471+
const allNodes = [astNode, ...extensionASTNodes];
472+
435473
return new GraphQLUnionType({
436474
name,
437475
description,
438-
types: () => this.buildUnionTypes([astNode]),
476+
types: () => this.buildUnionTypes(allNodes),
439477
astNode,
478+
extensionASTNodes,
440479
});
441-
case Kind.SCALAR_TYPE_DEFINITION:
480+
}
481+
case Kind.SCALAR_TYPE_DEFINITION: {
482+
const extensionASTNodes = (extensionNodes: any);
483+
442484
return new GraphQLScalarType({
443485
name,
444486
description,
445487
astNode,
488+
extensionASTNodes,
446489
});
447-
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
490+
}
491+
case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
492+
const extensionASTNodes = (extensionNodes: any);
493+
const allNodes = [astNode, ...extensionASTNodes];
494+
448495
return new GraphQLInputObjectType({
449496
name,
450497
description,
451-
fields: () => this.buildInputFieldMap([astNode]),
498+
fields: () => this.buildInputFieldMap(allNodes),
452499
astNode,
500+
extensionASTNodes,
453501
});
502+
}
454503
}
455504

456505
// Not reachable. All possible type definition nodes have been considered.

src/utilities/extendSchema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export function extendSchema(
156156
return type;
157157
});
158158

159-
const typeMap = astBuilder.buildTypeMap(typeDefs);
159+
const typeMap = astBuilder.buildTypeMap(typeDefs, typeExtsMap);
160160
const schemaConfig = schema.toConfig();
161161
for (const existingType of schemaConfig.types) {
162162
typeMap[existingType.name] = extendNamedType(existingType);

0 commit comments

Comments
 (0)