Skip to content

Commit a1ee52a

Browse files
Allows to add schema definition missing in the original schema (#1441)
Split out from #1438 Allows to inject directives and types into SDL-based schema: ```js import { GrapQLDateTime } from 'somepackage'; let schema = new GraphQLSchema({ types: [GrapQLDateTime], }); const ast = parse(` type Query { timestamp: DateTime } `); schema = extendSchema(schema, ast); ``` ```js let schema = new GraphQLSchema({ directives: [ new GraphQLDirective({ name: 'onSchema', locations: ['SCHEMA'], }), ], }); const ast = parse(` schema @onSchema { query: Foo } type Foo { bar: String } `); schema = extendSchema(schema, ast); ```
1 parent 732c90f commit a1ee52a

File tree

2 files changed

+72
-36
lines changed

2 files changed

+72
-36
lines changed

src/utilities/__tests__/extendSchema-test.js

+39-22
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ const SomeInputType = new GraphQLInputObjectType({
9393
}),
9494
});
9595

96+
const FooDirective = new GraphQLDirective({
97+
name: 'foo',
98+
args: {
99+
input: { type: SomeInputType },
100+
},
101+
locations: [
102+
DirectiveLocation.SCHEMA,
103+
DirectiveLocation.SCALAR,
104+
DirectiveLocation.OBJECT,
105+
DirectiveLocation.FIELD_DEFINITION,
106+
DirectiveLocation.ARGUMENT_DEFINITION,
107+
DirectiveLocation.INTERFACE,
108+
DirectiveLocation.UNION,
109+
DirectiveLocation.ENUM,
110+
DirectiveLocation.ENUM_VALUE,
111+
DirectiveLocation.INPUT_OBJECT,
112+
DirectiveLocation.INPUT_FIELD_DEFINITION,
113+
],
114+
});
115+
96116
const testSchema = new GraphQLSchema({
97117
query: new GraphQLObjectType({
98118
name: 'Query',
@@ -112,27 +132,7 @@ const testSchema = new GraphQLSchema({
112132
}),
113133
}),
114134
types: [FooType, BarType],
115-
directives: specifiedDirectives.concat([
116-
new GraphQLDirective({
117-
name: 'foo',
118-
args: {
119-
input: { type: SomeInputType },
120-
},
121-
locations: [
122-
DirectiveLocation.SCHEMA,
123-
DirectiveLocation.SCALAR,
124-
DirectiveLocation.OBJECT,
125-
DirectiveLocation.FIELD_DEFINITION,
126-
DirectiveLocation.ARGUMENT_DEFINITION,
127-
DirectiveLocation.INTERFACE,
128-
DirectiveLocation.UNION,
129-
DirectiveLocation.ENUM,
130-
DirectiveLocation.ENUM_VALUE,
131-
DirectiveLocation.INPUT_OBJECT,
132-
DirectiveLocation.INPUT_FIELD_DEFINITION,
133-
],
134-
}),
135-
]),
135+
directives: specifiedDirectives.concat([FooDirective]),
136136
});
137137

138138
function extendTestSchema(sdl, options) {
@@ -1271,7 +1271,7 @@ describe('extendSchema', () => {
12711271
expect(schema.getMutationType()).to.equal(null);
12721272
});
12731273

1274-
it('does not allow new schema within an extension', () => {
1274+
it('does not allow overriding schema within an extension', () => {
12751275
const sdl = `
12761276
schema {
12771277
mutation: Mutation
@@ -1286,6 +1286,23 @@ describe('extendSchema', () => {
12861286
);
12871287
});
12881288

1289+
it('adds schema definition missing in the original schema', () => {
1290+
let schema = new GraphQLSchema({
1291+
directives: [FooDirective],
1292+
types: [FooType],
1293+
});
1294+
expect(schema.getQueryType()).to.equal(undefined);
1295+
1296+
const ast = parse(`
1297+
schema @foo {
1298+
query: Foo
1299+
}
1300+
`);
1301+
schema = extendSchema(schema, ast);
1302+
const queryType = schema.getQueryType();
1303+
expect(queryType).to.have.property('name', 'Foo');
1304+
});
1305+
12891306
it('adds new root types via schema extension', () => {
12901307
const schema = extendTestSchema(`
12911308
extend schema {

src/utilities/extendSchema.js

+33-14
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import type {
5353
DocumentNode,
5454
DirectiveDefinitionNode,
5555
SchemaExtensionNode,
56+
SchemaDefinitionNode,
5657
} from '../language/ast';
5758

5859
type Options = {|
@@ -106,18 +107,28 @@ export function extendSchema(
106107
// have the same name. For example, a type named "skip".
107108
const directiveDefinitions: Array<DirectiveDefinitionNode> = [];
108109

110+
let schemaDef: ?SchemaDefinitionNode;
109111
// Schema extensions are collected which may add additional operation types.
110112
const schemaExtensions: Array<SchemaExtensionNode> = [];
111113

112114
for (let i = 0; i < documentAST.definitions.length; i++) {
113115
const def = documentAST.definitions[i];
114116
switch (def.kind) {
115117
case Kind.SCHEMA_DEFINITION:
116-
// Sanity check that a schema extension is not defining a new schema
117-
throw new GraphQLError(
118-
'Cannot define a new schema within a schema extension.',
119-
[def],
120-
);
118+
// Sanity check that a schema extension is not overriding the schema
119+
if (
120+
schema.astNode ||
121+
schema.getQueryType() ||
122+
schema.getMutationType() ||
123+
schema.getSubscriptionType()
124+
) {
125+
throw new GraphQLError(
126+
'Cannot define a new schema within a schema extension.',
127+
[def],
128+
);
129+
}
130+
schemaDef = def;
131+
break;
121132
case Kind.SCHEMA_EXTENSION:
122133
schemaExtensions.push(def);
123134
break;
@@ -184,7 +195,8 @@ export function extendSchema(
184195
Object.keys(typeExtensionsMap).length === 0 &&
185196
Object.keys(typeDefinitionMap).length === 0 &&
186197
directiveDefinitions.length === 0 &&
187-
schemaExtensions.length === 0
198+
schemaExtensions.length === 0 &&
199+
!schemaDef
188200
) {
189201
return schema;
190202
}
@@ -216,19 +228,28 @@ export function extendSchema(
216228
subscription: extendMaybeNamedType(schema.getSubscriptionType()),
217229
};
218230

219-
// Then, incorporate all schema extensions.
231+
if (schemaDef) {
232+
for (const { operation, type } of schemaDef.operationTypes) {
233+
if (operationTypes[operation]) {
234+
throw new Error(`Must provide only one ${operation} type in schema.`);
235+
}
236+
// Note: While this could make early assertions to get the correctly
237+
// typed values, that would throw immediately while type system
238+
// validation with validateSchema() will produce more actionable results.
239+
operationTypes[operation] = (astBuilder.buildType(type): any);
240+
}
241+
}
242+
// Then, incorporate schema definition and all schema extensions.
220243
for (const schemaExtension of schemaExtensions) {
221244
if (schemaExtension.operationTypes) {
222-
for (const operationType of schemaExtension.operationTypes) {
223-
const operation = operationType.operation;
245+
for (const { operation, type } of schemaExtension.operationTypes) {
224246
if (operationTypes[operation]) {
225247
throw new Error(`Must provide only one ${operation} type in schema.`);
226248
}
227-
const typeRef = operationType.type;
228249
// Note: While this could make early assertions to get the correctly
229250
// typed values, that would throw immediately while type system
230251
// validation with validateSchema() will produce more actionable results.
231-
operationTypes[operation] = (astBuilder.buildType(typeRef): any);
252+
operationTypes[operation] = (astBuilder.buildType(type): any);
232253
}
233254
}
234255
}
@@ -254,9 +275,7 @@ export function extendSchema(
254275

255276
// Then produce and return a Schema with these types.
256277
return new GraphQLSchema({
257-
query: operationTypes.query,
258-
mutation: operationTypes.mutation,
259-
subscription: operationTypes.subscription,
278+
...operationTypes,
260279
types,
261280
directives: getMergedDirectives(),
262281
astNode: schema.astNode,

0 commit comments

Comments
 (0)