@@ -15,6 +15,7 @@ import {
15
15
isIntrospectionType ,
16
16
GraphQLSchema ,
17
17
GraphQLError ,
18
+ VariableDefinitionNode ,
18
19
} from 'graphql' ;
19
20
import { GraphQLSchemaValidationError } from 'apollo-graphql' ;
20
21
import { composeAndValidate , ServiceDefinition } from '@apollo/federation' ;
@@ -38,6 +39,7 @@ import { serializeQueryPlan, QueryPlan, OperationContext } from './QueryPlan';
38
39
import { GraphQLDataSource } from './datasources/types' ;
39
40
import { RemoteGraphQLDataSource } from './datasources/RemoteGraphQLDataSource' ;
40
41
import { HeadersInit } from 'node-fetch' ;
42
+ import { getVariableValues } from 'graphql/execution/values' ;
41
43
42
44
export type ServiceEndpointDefinition = Pick < ServiceDefinition , 'name' | 'url' > ;
43
45
@@ -132,6 +134,11 @@ export type UpdateServiceDefinitions = (
132
134
133
135
type Await < T > = T extends Promise < infer U > ? U : T ;
134
136
137
+ type RequestContext < TContext > = WithRequired <
138
+ GraphQLRequestContext < TContext > ,
139
+ 'document' | 'queryHash'
140
+ > ;
141
+
135
142
export class ApolloGateway implements GraphQLService {
136
143
public schema ?: GraphQLSchema ;
137
144
protected serviceMap : ServiceMap = Object . create ( null ) ;
@@ -439,10 +446,7 @@ export class ApolloGateway implements GraphQLService {
439
446
// are unlikely to show up as GraphQLErrors. Do we need to use
440
447
// formatApolloErrors or something?
441
448
public executor = async < TContext > (
442
- requestContext : WithRequired <
443
- GraphQLRequestContext < TContext > ,
444
- 'document' | 'operation' | 'queryHash'
445
- > ,
449
+ requestContext : RequestContext < TContext > ,
446
450
) : Promise < GraphQLExecutionResult > => {
447
451
const { request, document, queryHash } = requestContext ;
448
452
const queryPlanStoreKey = queryHash + ( request . operationName || '' ) ;
@@ -451,7 +455,19 @@ export class ApolloGateway implements GraphQLService {
451
455
document ,
452
456
request . operationName ,
453
457
) ;
454
- let queryPlan ;
458
+
459
+ // No need to build a query plan if we know the request is invalid beforehand
460
+ // In the future, this should be controlled by the requestPipeline
461
+ const validationErrors = this . validateIncomingRequest (
462
+ requestContext ,
463
+ operationContext ,
464
+ ) ;
465
+
466
+ if ( validationErrors . length > 0 ) {
467
+ return { errors : validationErrors } ;
468
+ }
469
+
470
+ let queryPlan : QueryPlan | undefined ;
455
471
if ( this . queryPlanStore ) {
456
472
queryPlan = await this . queryPlanStore . get ( queryPlanStoreKey ) ;
457
473
}
@@ -510,6 +526,25 @@ export class ApolloGateway implements GraphQLService {
510
526
return response ;
511
527
} ;
512
528
529
+ protected validateIncomingRequest < TContext > (
530
+ requestContext : RequestContext < TContext > ,
531
+ operationContext : OperationContext ,
532
+ ) {
533
+ // casting out of `readonly`
534
+ const variableDefinitions = operationContext . operation
535
+ . variableDefinitions as VariableDefinitionNode [ ] | undefined ;
536
+
537
+ if ( ! variableDefinitions ) return [ ] ;
538
+
539
+ const { errors } = getVariableValues (
540
+ operationContext . schema ,
541
+ variableDefinitions ,
542
+ requestContext . request . variables ! ,
543
+ ) ;
544
+
545
+ return errors || [ ] ;
546
+ }
547
+
513
548
private initializeQueryPlanStore ( ) : void {
514
549
this . queryPlanStore = new InMemoryLRUCache < QueryPlan > ( {
515
550
// Create ~about~ a 30MiB InMemoryLRUCache. This is less than precise
0 commit comments