@@ -102,6 +102,18 @@ const collectSubfields = memoize3(
102
102
* 3) inline fragment "spreads" e.g. `...on Type { a }`
103
103
*/
104
104
105
+ type NormalizedExecutableDocument =
106
+ | {
107
+ errors : ReadonlyArray < GraphQLError > ;
108
+ operation ?: never ;
109
+ fragments ?: never ;
110
+ }
111
+ | {
112
+ operation : OperationDefinitionNode ;
113
+ fragments : ObjMap < FragmentDefinitionNode > ;
114
+ errors ?: never ;
115
+ } ;
116
+
105
117
/**
106
118
* Data that must be available at all points during query execution.
107
119
*
@@ -197,22 +209,59 @@ export interface SubscriptionArgs extends ExecutionArgs {}
197
209
* rather than a promise that resolves to the ExecutionResult with the errors.
198
210
*
199
211
*/
200
- export function executeRequest (
201
- args : ExecutionArgs ,
202
- ) :
212
+ export function executeRequest ( {
213
+ schema,
214
+ document,
215
+ rootValue,
216
+ contextValue,
217
+ variableValues,
218
+ operationName,
219
+ disableSubscription,
220
+ fieldResolver,
221
+ typeResolver,
222
+ subscribeFieldResolver,
223
+ } : ExecutionArgs ) :
203
224
| ExecutionResult
204
225
| Promise < ExecutionResult | AsyncGenerator < ExecutionResult , void , void > > {
205
- const exeContext = buildExecutionContext ( args ) ;
226
+ // If arguments are missing or incorrect, throw an error.
227
+ assertValidExecutionArguments ( schema , document , variableValues ) ;
206
228
207
- // Return early errors if execution context failed.
208
- if ( ! ( 'schema' in exeContext ) ) {
209
- return { errors : exeContext } ;
229
+ // If an error is encountered while selecting an operation, return it.
230
+ const normalizedExecutableDocument = getNormalizedExecutableDefinitions (
231
+ document ,
232
+ operationName ,
233
+ ) ;
234
+ if ( normalizedExecutableDocument . errors ) {
235
+ return { errors : normalizedExecutableDocument . errors } ;
210
236
}
211
237
212
- if (
213
- ! args . disableSubscription &&
214
- exeContext . operation . operation === 'subscription'
215
- ) {
238
+ const { operation, fragments } = normalizedExecutableDocument ;
239
+
240
+ // If errors are encountered while coercing variable values, return them.
241
+ const coercedVariableValues = getCoercedVariableValues (
242
+ schema ,
243
+ operation ,
244
+ variableValues ,
245
+ ) ;
246
+ if ( coercedVariableValues . errors ) {
247
+ return { errors : coercedVariableValues . errors } ;
248
+ }
249
+
250
+ // Set up the execution context
251
+ const exeContext = {
252
+ schema,
253
+ fragments,
254
+ rootValue,
255
+ contextValue,
256
+ operation,
257
+ coercedVariableValues : coercedVariableValues . coerced ,
258
+ fieldResolver : fieldResolver ?? defaultFieldResolver ,
259
+ typeResolver : typeResolver ?? defaultTypeResolver ,
260
+ subscribeFieldResolver,
261
+ errors : [ ] ,
262
+ } ;
263
+
264
+ if ( ! disableSubscription && operation . operation === 'subscription' ) {
216
265
return executeSubscription ( exeContext ) ;
217
266
}
218
267
@@ -326,41 +375,31 @@ export function assertValidExecutionArguments(
326
375
}
327
376
328
377
/**
329
- * Constructs a ExecutionContext object from the arguments passed to
330
- * executeRequest, which we will pass throughout the other execution methods .
378
+ * Normalizes executable definitions within a document based on the given
379
+ * operation name .
331
380
*
332
- * Throws a GraphQLError if a valid execution context cannot be created .
381
+ * Returns a GraphQLError if a single matching operation cannot be found .
333
382
*
334
383
* @internal
335
384
*/
336
- export function buildExecutionContext (
337
- args : ExecutionArgs ,
338
- ) : ReadonlyArray < GraphQLError > | ExecutionContext {
339
- const {
340
- schema,
341
- document,
342
- rootValue,
343
- contextValue,
344
- variableValues,
345
- operationName,
346
- fieldResolver,
347
- typeResolver,
348
- subscribeFieldResolver,
349
- } = args ;
350
- assertValidExecutionArguments ( schema , document , variableValues ) ;
351
-
385
+ export function getNormalizedExecutableDefinitions (
386
+ document : DocumentNode ,
387
+ operationName ?: Maybe < string > ,
388
+ ) : NormalizedExecutableDocument {
352
389
let operation : OperationDefinitionNode | undefined ;
353
390
const fragments : ObjMap < FragmentDefinitionNode > = Object . create ( null ) ;
354
391
for ( const definition of document . definitions ) {
355
392
switch ( definition . kind ) {
356
393
case Kind . OPERATION_DEFINITION :
357
394
if ( operationName == null ) {
358
395
if ( operation !== undefined ) {
359
- return [
360
- new GraphQLError (
361
- 'Must provide operation name if query contains multiple operations.' ,
362
- ) ,
363
- ] ;
396
+ return {
397
+ errors : [
398
+ new GraphQLError (
399
+ 'Must provide operation name if query contains multiple operations.' ,
400
+ ) ,
401
+ ] ,
402
+ } ;
364
403
}
365
404
operation = definition ;
366
405
} else if ( definition . name ?. value === operationName ) {
@@ -375,34 +414,71 @@ export function buildExecutionContext(
375
414
376
415
if ( ! operation ) {
377
416
if ( operationName != null ) {
378
- return [ new GraphQLError ( `Unknown operation named "${ operationName } ".` ) ] ;
417
+ return {
418
+ errors : [
419
+ new GraphQLError ( `Unknown operation named "${ operationName } ".` ) ,
420
+ ] ,
421
+ } ;
379
422
}
380
- return [ new GraphQLError ( 'Must provide an operation.' ) ] ;
423
+ return { errors : [ new GraphQLError ( 'Must provide an operation.' ) ] } ;
381
424
}
382
425
426
+ return {
427
+ operation,
428
+ fragments,
429
+ } ;
430
+ }
431
+
432
+ /**
433
+ * Gets coerced variable values based on a given schema and operation.
434
+ *
435
+ * A thin wrapper around getVariableValues.
436
+ *
437
+ * @internal
438
+ */
439
+ function getCoercedVariableValues (
440
+ schema : GraphQLSchema ,
441
+ operation : OperationDefinitionNode ,
442
+ variableValues : Maybe < { readonly [ variable : string ] : unknown } > ,
443
+ ) {
383
444
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
384
445
const variableDefinitions = operation . variableDefinitions ?? [ ] ;
385
446
386
- const coercedVariableValues = getVariableValues (
387
- schema ,
388
- variableDefinitions ,
389
- variableValues ?? { } ,
390
- {
391
- maxErrors : 50 ,
392
- } ,
393
- ) ;
447
+ return getVariableValues ( schema , variableDefinitions , variableValues ?? { } , {
448
+ maxErrors : 50 ,
449
+ } ) ;
450
+ }
394
451
395
- if ( coercedVariableValues . errors ) {
396
- return coercedVariableValues . errors ;
397
- }
452
+ /**
453
+ * Constructs a ExecutionContext object from the arguments passed to
454
+ * executeRequest, the normalized executable definitions, and the coerced
455
+ * variable values. The ExecutionContext will be passed throughout the
456
+ * other execution methods.
457
+ *
458
+ * @internal
459
+ */
460
+ export function buildExecutionContext (
461
+ args : ExecutionArgs ,
462
+ operation : OperationDefinitionNode ,
463
+ fragments : ObjMap < FragmentDefinitionNode > ,
464
+ coercedVariableValues : { [ variable : string ] : unknown } ,
465
+ ) : ExecutionContext {
466
+ const {
467
+ schema,
468
+ rootValue,
469
+ contextValue,
470
+ fieldResolver,
471
+ typeResolver,
472
+ subscribeFieldResolver,
473
+ } = args ;
398
474
399
475
return {
400
476
schema,
401
477
fragments,
402
478
rootValue,
403
479
contextValue,
404
480
operation,
405
- coercedVariableValues : coercedVariableValues . coerced ,
481
+ coercedVariableValues,
406
482
fieldResolver : fieldResolver ?? defaultFieldResolver ,
407
483
typeResolver : typeResolver ?? defaultTypeResolver ,
408
484
subscribeFieldResolver,
0 commit comments