@@ -19,7 +19,7 @@ import {
19
19
GraphQLField , isCompositeType , GraphQLCompositeType , GraphQLFieldMap ,
20
20
GraphQLSchema , DocumentNode , TypeInfo ,
21
21
visit , visitWithTypeInfo ,
22
- GraphQLDirective ,
22
+ GraphQLDirective , isAbstractType ,
23
23
} from 'graphql' ;
24
24
import {
25
25
GraphQLUnionType ,
@@ -48,6 +48,11 @@ export type ComplexityEstimator = (options: ComplexityEstimatorArgs) => number |
48
48
// Complexity can be anything that is supported by the configured estimators
49
49
export type Complexity = any ;
50
50
51
+ // Map of complexities for possible types (of Union, Interface types)
52
+ type ComplexityMap = {
53
+ [ typeName : string ] : number ,
54
+ }
55
+
51
56
export interface QueryComplexityOptions {
52
57
// The maximum allowed query complexity, queries above this threshold will be rejected
53
58
maximumComplexity : number ,
@@ -179,16 +184,25 @@ export default class QueryComplexity {
179
184
nodeComplexity (
180
185
node : FieldNode | FragmentDefinitionNode | InlineFragmentNode | OperationDefinitionNode ,
181
186
typeDef : GraphQLObjectType | GraphQLInterfaceType | GraphQLUnionType ,
182
- complexity : number = 0
183
187
) : number {
184
188
if ( node . selectionSet ) {
185
189
let fields :GraphQLFieldMap < any , any > = { } ;
186
190
if ( typeDef instanceof GraphQLObjectType || typeDef instanceof GraphQLInterfaceType ) {
187
191
fields = typeDef . getFields ( ) ;
188
192
}
189
- return complexity + node . selectionSet . selections . reduce (
190
- ( total : number , childNode : FieldNode | FragmentSpreadNode | InlineFragmentNode ) => {
191
- let nodeComplexity = 0 ;
193
+
194
+ // Determine all possible types of the current node
195
+ let possibleTypeNames : string [ ] ;
196
+ if ( isAbstractType ( typeDef ) ) {
197
+ possibleTypeNames = this . context . getSchema ( ) . getPossibleTypes ( typeDef ) . map ( t => t . name ) ;
198
+ } else {
199
+ possibleTypeNames = [ typeDef . name ] ;
200
+ }
201
+
202
+ // Collect complexities for all possible types individually
203
+ const selectionSetComplexities : ComplexityMap = node . selectionSet . selections . reduce (
204
+ ( complexities : ComplexityMap , childNode : FieldNode | FragmentSpreadNode | InlineFragmentNode ) => {
205
+ // let nodeComplexity = 0;
192
206
193
207
let includeNode = true ;
194
208
let skipNode = false ;
@@ -210,7 +224,7 @@ export default class QueryComplexity {
210
224
} ) ;
211
225
212
226
if ( ! includeNode || skipNode ) {
213
- return total ;
227
+ return complexities ;
214
228
}
215
229
216
230
switch ( childNode . kind ) {
@@ -248,7 +262,11 @@ export default class QueryComplexity {
248
262
const tmpComplexity = estimator ( estimatorArgs ) ;
249
263
250
264
if ( typeof tmpComplexity === 'number' && ! isNaN ( tmpComplexity ) ) {
251
- nodeComplexity = tmpComplexity ;
265
+ complexities = addComplexities (
266
+ tmpComplexity ,
267
+ complexities ,
268
+ possibleTypeNames ,
269
+ ) ;
252
270
return true ;
253
271
}
254
272
@@ -269,30 +287,69 @@ export default class QueryComplexity {
269
287
const fragmentType = assertCompositeType (
270
288
this . context . getSchema ( ) . getType ( fragment . typeCondition . name . value )
271
289
) ;
272
- nodeComplexity = this . nodeComplexity ( fragment , fragmentType ) ;
290
+ const nodeComplexity = this . nodeComplexity ( fragment , fragmentType ) ;
291
+ if ( isAbstractType ( fragmentType ) ) {
292
+ // Add fragment complexity for all possible types
293
+ complexities = addComplexities (
294
+ nodeComplexity ,
295
+ complexities ,
296
+ this . context . getSchema ( ) . getPossibleTypes ( fragmentType ) . map ( t => t . name ) ,
297
+ ) ;
298
+ } else {
299
+ // Add complexity for object type
300
+ complexities = addComplexities (
301
+ nodeComplexity ,
302
+ complexities ,
303
+ [ fragmentType . name ] ,
304
+ ) ;
305
+ }
273
306
break ;
274
307
}
275
308
case Kind . INLINE_FRAGMENT : {
276
309
let inlineFragmentType = typeDef ;
277
310
if ( childNode . typeCondition && childNode . typeCondition . name ) {
278
- // $FlowFixMe: Not sure why flow thinks this can still be NULL
279
311
inlineFragmentType = assertCompositeType (
280
312
this . context . getSchema ( ) . getType ( childNode . typeCondition . name . value )
281
313
) ;
282
314
}
283
315
284
- nodeComplexity = this . nodeComplexity ( childNode , inlineFragmentType ) ;
316
+ const nodeComplexity = this . nodeComplexity ( childNode , inlineFragmentType ) ;
317
+ if ( isAbstractType ( inlineFragmentType ) ) {
318
+ // Add fragment complexity for all possible types
319
+ complexities = addComplexities (
320
+ nodeComplexity ,
321
+ complexities ,
322
+ this . context . getSchema ( ) . getPossibleTypes ( inlineFragmentType ) . map ( t => t . name ) ,
323
+ ) ;
324
+ } else {
325
+ // Add complexity for object type
326
+ complexities = addComplexities (
327
+ nodeComplexity ,
328
+ complexities ,
329
+ [ inlineFragmentType . name ] ,
330
+ ) ;
331
+ }
285
332
break ;
286
333
}
287
334
default : {
288
- nodeComplexity = this . nodeComplexity ( childNode , typeDef ) ;
335
+ complexities = addComplexities (
336
+ this . nodeComplexity ( childNode , typeDef ) ,
337
+ complexities ,
338
+ possibleTypeNames ,
339
+ ) ;
289
340
break ;
290
341
}
291
342
}
292
- return Math . max ( nodeComplexity , 0 ) + total ;
293
- } , complexity ) ;
343
+
344
+ return complexities ;
345
+ } , { } ) ;
346
+ // Only return max complexity of all possible types
347
+ if ( ! selectionSetComplexities ) {
348
+ return NaN ;
349
+ }
350
+ return Math . max ( ...Object . values ( selectionSetComplexities ) , 0 ) ;
294
351
}
295
- return complexity ;
352
+ return 0 ;
296
353
}
297
354
298
355
createError ( ) : GraphQLError {
@@ -308,3 +365,24 @@ export default class QueryComplexity {
308
365
) ) ;
309
366
}
310
367
}
368
+
369
+ /**
370
+ * Adds a complexity to the complexity map for all possible types
371
+ * @param complexity
372
+ * @param complexityMap
373
+ * @param possibleTypes
374
+ */
375
+ function addComplexities (
376
+ complexity : number ,
377
+ complexityMap : ComplexityMap ,
378
+ possibleTypes : string [ ] ,
379
+ ) : ComplexityMap {
380
+ for ( const type of possibleTypes ) {
381
+ if ( complexityMap . hasOwnProperty ( type ) ) {
382
+ complexityMap [ type ] = complexityMap [ type ] + complexity ;
383
+ } else {
384
+ complexityMap [ type ] = complexity ;
385
+ }
386
+ }
387
+ return complexityMap ;
388
+ }
0 commit comments