Skip to content

Commit e834097

Browse files
authored
[RFC] Allows use of generated schemas for execution. (#469)
Right now our generated schemas (built from introspection, built from AST) explicitly do not allow their use in execution. They throw as soon as any resolver function is called. This conservative behavior was appropriate for early versions where we wanted to limit the surface area under use. Today it's becoming more clear that there are immediately valuable use cases for using generated schema in execution.
1 parent 5e3d377 commit e834097

File tree

7 files changed

+55
-35
lines changed

7 files changed

+55
-35
lines changed

src/type/definition.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ export type GraphQLUnionTypeConfig = {
704704
* the default implementation will call `isTypeOf` on each implementing
705705
* Object type.
706706
*/
707-
resolveType?: GraphQLTypeResolveFn;
707+
resolveType?: ?GraphQLTypeResolveFn;
708708
description?: ?string;
709709
};
710710

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import { parse } from '../../language';
1313
import { printSchema } from '../schemaPrinter';
1414
import { buildASTSchema } from '../buildASTSchema';
1515
import {
16+
graphql,
1617
GraphQLSkipDirective,
1718
GraphQLIncludeDirective,
1819
GraphQLDeprecatedDirective,
19-
} from '../../type/directives';
20+
} from '../../';
2021

2122
/**
2223
* This function does a full cycle of going from a
@@ -32,6 +33,23 @@ function cycleOutput(body) {
3233
}
3334

3435
describe('Schema Builder', () => {
36+
37+
it('can use built schema for limited execution', async () => {
38+
const schema = buildASTSchema(parse(`
39+
schema { query: Query }
40+
type Query {
41+
str: String
42+
}
43+
`));
44+
45+
const result = await graphql(
46+
schema,
47+
'{ str }',
48+
{ str: 123 }
49+
);
50+
expect(result.data).to.deep.equal({ str: '123' });
51+
});
52+
3553
it('Simple type', () => {
3654
const body = `
3755
schema {

src/utilities/__tests__/buildClientSchema-test.js

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ describe('Type System: build schema from introspection', () => {
644644
await testSchema(schema);
645645
});
646646

647-
it('cannot use client schema for general execution', async () => {
647+
it('can use client schema for limited execution', async () => {
648648
const customScalar = new GraphQLScalarType({
649649
name: 'CustomScalar',
650650
serialize: () => null,
@@ -670,20 +670,13 @@ describe('Type System: build schema from introspection', () => {
670670

671671
const result = await graphql(
672672
clientSchema,
673-
'query NoNo($v: CustomScalar) { foo(custom1: 123, custom2: $v) }',
674-
{ foo: 'bar' },
673+
'query Limited($v: CustomScalar) { foo(custom1: 123, custom2: $v) }',
674+
{ foo: 'bar', unused: 'value' },
675675
null,
676676
{ v: 'baz' }
677677
);
678-
expect(result).to.containSubset({
679-
data: {
680-
foo: null,
681-
},
682-
errors: [
683-
{ message: 'Client Schema cannot be used for execution.',
684-
locations: [ { line: 1, column: 32 } ] }
685-
]
686-
});
678+
679+
expect(result.data).to.deep.equal({ foo: 'bar' });
687680
});
688681

689682
describe('throws when given incomplete introspection', () => {

src/utilities/__tests__/extendSchema-test.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('extendSchema', () => {
115115
expect(printSchema(testSchema)).to.not.contain('newField');
116116
});
117117

118-
it('cannot be used for execution', async () => {
118+
it('can be used for limited execution', async () => {
119119
const ast = parse(`
120120
extend type Query {
121121
newField: String
@@ -124,11 +124,12 @@ describe('extendSchema', () => {
124124
const extendedSchema = extendSchema(testSchema, ast);
125125
const clientQuery = parse('{ newField }');
126126

127-
const result = await execute(extendedSchema, clientQuery);
128-
expect(result.data.newField).to.equal(null);
129-
expect(result.errors).to.containSubset([ {
130-
message: 'Client Schema cannot be used for execution.'
131-
} ]);
127+
const result = await execute(
128+
extendedSchema,
129+
clientQuery,
130+
{ newField: 123 }
131+
);
132+
expect(result.data).to.deep.equal({ newField: '123' });
132133
});
133134

134135
it('can describe the extended fields', async () => {

src/utilities/buildASTSchema.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,8 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
405405
return new GraphQLInterfaceType({
406406
name: typeName,
407407
description: getDescription(def),
408-
resolveType: () => null,
409408
fields: () => makeFieldDefMap(def),
409+
resolveType: cannotExecuteSchema,
410410
});
411411
}
412412

@@ -431,8 +431,8 @@ export function buildASTSchema(ast: Document): GraphQLSchema {
431431
return new GraphQLUnionType({
432432
name: def.name.value,
433433
description: getDescription(def),
434-
resolveType: () => null,
435434
types: def.types.map(t => produceObjectType(t)),
435+
resolveType: cannotExecuteSchema,
436436
});
437437
}
438438

@@ -517,3 +517,9 @@ function leadingSpaces(str) {
517517
}
518518
return i;
519519
}
520+
521+
function cannotExecuteSchema() {
522+
throw new Error(
523+
'Generated Schema cannot use Interface or Union types for execution.'
524+
);
525+
}

src/utilities/buildClientSchema.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export function buildClientSchema(
226226
return new GraphQLScalarType({
227227
name: scalarIntrospection.name,
228228
description: scalarIntrospection.description,
229-
serialize: () => null,
229+
serialize: id => id,
230230
// Note: validation calls the parse functions to determine if a
231231
// literal value is correct. Returning null would cause use of custom
232232
// scalars to always fail validation. Returning false causes them to
@@ -305,7 +305,6 @@ export function buildClientSchema(
305305
deprecationReason: fieldIntrospection.deprecationReason,
306306
type: getOutputType(fieldIntrospection.type),
307307
args: buildInputValueDefMap(fieldIntrospection.args),
308-
resolve: cannotExecuteClientSchema,
309308
})
310309
);
311310
}
@@ -393,5 +392,7 @@ export function buildClientSchema(
393392
}
394393

395394
function cannotExecuteClientSchema() {
396-
throw new Error('Client Schema cannot be used for execution.');
395+
throw new Error(
396+
'Client Schema cannot use Interface or Union types for execution.'
397+
);
397398
}

src/utilities/extendSchema.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ export function extendSchema(
350350
description: type.description,
351351
interfaces: () => extendImplementedInterfaces(type),
352352
fields: () => extendFieldMap(type),
353+
isTypeOf: type.isTypeOf,
353354
});
354355
}
355356

@@ -360,7 +361,7 @@ export function extendSchema(
360361
name: type.name,
361362
description: type.description,
362363
fields: () => extendFieldMap(type),
363-
resolveType: cannotExecuteClientSchema,
364+
resolveType: type.resolveType,
364365
});
365366
}
366367

@@ -369,7 +370,7 @@ export function extendSchema(
369370
name: type.name,
370371
description: type.description,
371372
types: type.getTypes().map(getTypeFromDef),
372-
resolveType: cannotExecuteClientSchema,
373+
resolveType: type.resolveType,
373374
});
374375
}
375376

@@ -409,7 +410,7 @@ export function extendSchema(
409410
deprecationReason: field.deprecationReason,
410411
type: extendFieldType(field.type),
411412
args: keyMap(field.args, arg => arg.name),
412-
resolve: cannotExecuteClientSchema,
413+
resolve: field.resolve,
413414
};
414415
});
415416

@@ -430,7 +431,6 @@ export function extendSchema(
430431
description: getDescription(field),
431432
type: buildOutputFieldType(field.type),
432433
args: buildInputValues(field.arguments),
433-
resolve: cannotExecuteClientSchema,
434434
};
435435
});
436436
});
@@ -475,7 +475,7 @@ export function extendSchema(
475475
name: typeAST.name.value,
476476
description: getDescription(typeAST),
477477
fields: () => buildFieldMap(typeAST),
478-
resolveType: cannotExecuteClientSchema,
478+
resolveType: cannotExecuteExtendedSchema,
479479
});
480480
}
481481

@@ -484,15 +484,15 @@ export function extendSchema(
484484
name: typeAST.name.value,
485485
description: getDescription(typeAST),
486486
types: typeAST.types.map(getObjectTypeFromAST),
487-
resolveType: cannotExecuteClientSchema,
487+
resolveType: cannotExecuteExtendedSchema,
488488
});
489489
}
490490

491491
function buildScalarType(typeAST: ScalarTypeDefinition) {
492492
return new GraphQLScalarType({
493493
name: typeAST.name.value,
494494
description: getDescription(typeAST),
495-
serialize: () => null,
495+
serialize: id => id,
496496
// Note: validation calls the parse functions to determine if a
497497
// literal value is correct. Returning null would cause use of custom
498498
// scalars to always fail validation. Returning false causes them to
@@ -543,7 +543,6 @@ export function extendSchema(
543543
type: buildOutputFieldType(field.type),
544544
description: getDescription(field),
545545
args: buildInputValues(field.arguments),
546-
resolve: cannotExecuteClientSchema,
547546
})
548547
);
549548
}
@@ -588,6 +587,8 @@ export function extendSchema(
588587
}
589588
}
590589

591-
function cannotExecuteClientSchema() {
592-
throw new Error('Client Schema cannot be used for execution.');
590+
function cannotExecuteExtendedSchema() {
591+
throw new Error(
592+
'Extended Schema cannot use Interface or Union types for execution.'
593+
);
593594
}

0 commit comments

Comments
 (0)