7
7
* @flow strict
8
8
*/
9
9
10
- import {
11
- isObjectType ,
12
- isInterfaceType ,
13
- isUnionType ,
14
- isEnumType ,
15
- isInputObjectType ,
16
- isNonNullType ,
17
- isNamedType ,
18
- isInputType ,
19
- isOutputType ,
20
- } from './definition' ;
21
10
import type {
22
- GraphQLObjectType ,
23
- GraphQLInterfaceType ,
24
- GraphQLUnionType ,
11
+ ASTNode ,
12
+ DirectiveNode ,
13
+ EnumValueDefinitionNode ,
14
+ FieldDefinitionNode ,
15
+ InputValueDefinitionNode ,
16
+ NamedTypeNode ,
17
+ TypeNode ,
18
+ } from '../language/ast' ;
19
+ import type {
25
20
GraphQLEnumType ,
26
21
GraphQLInputObjectType ,
22
+ GraphQLInterfaceType ,
23
+ GraphQLObjectType ,
24
+ GraphQLUnionType ,
27
25
} from './definition' ;
28
- import { isDirective } from './directives' ;
29
26
import type { GraphQLDirective } from './directives' ;
30
- import { isIntrospectionType } from './introspection' ;
31
- import { isSchema } from './schema' ;
32
27
import type { GraphQLSchema } from './schema' ;
33
- import inspect from '../jsutils/inspect' ;
28
+
29
+ import { GraphQLError } from '../error/GraphQLError' ;
34
30
import find from '../jsutils/find' ;
31
+ import inspect from '../jsutils/inspect' ;
35
32
import invariant from '../jsutils/invariant' ;
36
33
import objectValues from '../jsutils/objectValues' ;
37
- import { GraphQLError } from '../error/GraphQLError' ;
38
- import type {
39
- ASTNode ,
40
- FieldDefinitionNode ,
41
- EnumValueDefinitionNode ,
42
- InputValueDefinitionNode ,
43
- NamedTypeNode ,
44
- TypeNode ,
45
- } from '../language/ast' ;
46
34
import { isValidNameError } from '../utilities/assertValidName' ;
47
35
import { isEqualType , isTypeSubTypeOf } from '../utilities/typeComparators' ;
36
+ import {
37
+ isEnumType ,
38
+ isInputObjectType ,
39
+ isInputType ,
40
+ isInterfaceType ,
41
+ isNamedType ,
42
+ isNonNullType ,
43
+ isObjectType ,
44
+ isOutputType ,
45
+ isUnionType ,
46
+ } from './definition' ;
47
+ import { isDirective } from './directives' ;
48
+ import { isIntrospectionType } from './introspection' ;
49
+ import { isSchema } from './schema' ;
48
50
49
51
/**
50
52
* Implements the "Type Validation" sub-sections of the specification's
@@ -70,7 +72,13 @@ export function validateSchema(
70
72
// Validate the schema, producing a list of errors.
71
73
const context = new SchemaValidationContext ( schema ) ;
72
74
validateRootTypes ( context ) ;
73
- validateDirectives ( context ) ;
75
+ validateDirectiveDefinitions ( context ) ;
76
+
77
+ // Validate directives that are used on the schema
78
+ if ( schema . astNode && schema . astNode . directives ) {
79
+ validateNoDuplicateDirectives ( context , schema . astNode . directives ) ;
80
+ }
81
+
74
82
validateTypes ( context ) ;
75
83
76
84
// Persist the results of validation before returning to ensure validation
@@ -165,7 +173,10 @@ function getOperationTypeNode(
165
173
return type . astNode ;
166
174
}
167
175
168
- function validateDirectives ( context : SchemaValidationContext ) : void {
176
+ function validateDirectiveDefinitions ( context : SchemaValidationContext ) : void {
177
+ // Ensure no directive is defined multiple times
178
+ const directiveDefinitions = new Map ( ) ;
179
+
169
180
for ( const directive of context . schema . getDirectives ( ) ) {
170
181
// Ensure all directives are in fact GraphQL directives.
171
182
if ( ! isDirective ( directive ) ) {
@@ -175,6 +186,9 @@ function validateDirectives(context: SchemaValidationContext): void {
175
186
) ;
176
187
continue ;
177
188
}
189
+ const existingDefinitions = directiveDefinitions . get ( directive . name ) || [ ] ;
190
+ existingDefinitions . push ( directive ) ;
191
+ directiveDefinitions . set ( directive . name , existingDefinitions ) ;
178
192
179
193
// Ensure they are named correctly.
180
194
validateName ( context , directive ) ;
@@ -209,6 +223,15 @@ function validateDirectives(context: SchemaValidationContext): void {
209
223
}
210
224
}
211
225
}
226
+
227
+ for ( const [ directiveName , directiveList ] of directiveDefinitions ) {
228
+ if ( directiveList . length > 1 ) {
229
+ context . reportError (
230
+ `Directive @${ directiveName } defined multiple times.` ,
231
+ directiveList . map ( directive => directive . astNode ) . filter ( Boolean ) ,
232
+ ) ;
233
+ }
234
+ }
212
235
}
213
236
214
237
function validateName (
@@ -239,6 +262,8 @@ function validateTypes(context: SchemaValidationContext): void {
239
262
continue ;
240
263
}
241
264
265
+ validateNoDuplicateDirectives ( context , type . getDirectives ( ) ) ;
266
+
242
267
// Ensure it is named correctly (excluding introspection types).
243
268
if ( ! isIntrospectionType ( type ) ) {
244
269
validateName ( context , type ) ;
@@ -266,6 +291,28 @@ function validateTypes(context: SchemaValidationContext): void {
266
291
}
267
292
}
268
293
294
+ function validateNoDuplicateDirectives (
295
+ context : SchemaValidationContext ,
296
+ directives : $ReadOnlyArray < DirectiveNode > ,
297
+ ) : void {
298
+ const directivesNamed = new Map ( ) ;
299
+ for ( const directive of directives ) {
300
+ const directiveName = directive . name . value ;
301
+ const existingNodes = directivesNamed . get ( directiveName ) || [ ] ;
302
+ existingNodes . push ( directive ) ;
303
+ directivesNamed . set ( directiveName , existingNodes ) ;
304
+ }
305
+
306
+ for ( const [ directiveName , directiveList ] of directivesNamed ) {
307
+ if ( directiveList . length > 1 ) {
308
+ context . reportError (
309
+ `Directive @${ directiveName } used twice at the same location.` ,
310
+ directiveList ,
311
+ ) ;
312
+ }
313
+ }
314
+ }
315
+
269
316
function validateFields (
270
317
context : SchemaValidationContext ,
271
318
type : GraphQLObjectType | GraphQLInterfaceType ,
@@ -329,6 +376,16 @@ function validateFields(
329
376
getFieldArgTypeNode ( type , field . name , argName ) ,
330
377
) ;
331
378
}
379
+
380
+ // Ensure argument definition directives are valid
381
+ if ( arg . astNode && arg . astNode . directives ) {
382
+ validateNoDuplicateDirectives ( context , arg . astNode . directives ) ;
383
+ }
384
+ }
385
+
386
+ // Ensure any directives are valid
387
+ if ( field . astNode && field . astNode . directives ) {
388
+ validateNoDuplicateDirectives ( context , field . astNode . directives ) ;
332
389
}
333
390
}
334
391
}
@@ -520,6 +577,11 @@ function validateEnumValues(
520
577
enumValue . astNode ,
521
578
) ;
522
579
}
580
+
581
+ // Ensure valid directives
582
+ if ( enumValue . astNode && enumValue . astNode . directives ) {
583
+ validateNoDuplicateDirectives ( context , enumValue . astNode . directives ) ;
584
+ }
523
585
}
524
586
}
525
587
@@ -551,6 +613,11 @@ function validateInputFields(
551
613
field . astNode && field . astNode . type ,
552
614
) ;
553
615
}
616
+
617
+ // Ensure valid directives
618
+ if ( field . astNode && field . astNode . directives ) {
619
+ validateNoDuplicateDirectives ( context , field . astNode . directives ) ;
620
+ }
554
621
}
555
622
}
556
623
0 commit comments