4
4
SelectionSetNode ,
5
5
DefinitionNode ,
6
6
Kind ,
7
+ DirectiveNode ,
7
8
SelectionNode ,
9
+ getArgumentValues ,
8
10
} from 'graphql' ;
9
11
import { FieldWeight , TypeWeightObject , Variables } from '../@types/buildTypeWeights' ;
10
12
/**
@@ -30,14 +32,20 @@ import { FieldWeight, TypeWeightObject, Variables } from '../@types/buildTypeWei
30
32
class ASTParser {
31
33
typeWeights : TypeWeightObject ;
32
34
35
+ depth : number ;
36
+
37
+ maxDepth : number ;
38
+
33
39
variables : Variables ;
34
40
35
- fragmentCache : { [ index : string ] : number } ;
41
+ fragmentCache : { [ index : string ] : { complexity : number ; depth : number } } ;
36
42
37
43
constructor ( typeWeights : TypeWeightObject , variables : Variables ) {
38
44
this . typeWeights = typeWeights ;
39
45
this . variables = variables ;
40
46
this . fragmentCache = { } ;
47
+ this . depth = 0 ;
48
+ this . maxDepth = 0 ;
41
49
}
42
50
43
51
private calculateCost (
@@ -59,6 +67,8 @@ class ASTParser {
59
67
if ( node . arguments && typeof typeWeight === 'function' ) {
60
68
// FIXME: May never happen but what if weight is a function and arguments don't exist
61
69
calculatedWeight += typeWeight ( [ ...node . arguments ] , this . variables , selectionsCost ) ;
70
+ } else if ( typeof typeWeight === 'number' ) {
71
+ calculatedWeight += typeWeight + selectionsCost ;
62
72
} else {
63
73
calculatedWeight += this . typeWeights [ typeName ] . weight + selectionsCost ;
64
74
}
@@ -67,7 +77,7 @@ class ASTParser {
67
77
return complexity ;
68
78
}
69
79
70
- fieldNode ( node : FieldNode , parentName : string ) : number {
80
+ private fieldNode ( node : FieldNode , parentName : string ) : number {
71
81
try {
72
82
let complexity = 0 ;
73
83
const parentType = this . typeWeights [ parentName ] ;
@@ -78,7 +88,7 @@ class ASTParser {
78
88
}
79
89
let typeName : string | undefined ;
80
90
let typeWeight : FieldWeight | undefined ;
81
-
91
+ if ( node . name . value === '__typename' ) return complexity ;
82
92
if ( node . name . value in this . typeWeights ) {
83
93
// node is an object type n the typeWeight root
84
94
typeName = node . name . value ;
@@ -131,14 +141,60 @@ class ASTParser {
131
141
}
132
142
}
133
143
134
- selectionNode ( node : SelectionNode , parentName : string ) : number {
144
+ /**
145
+ * Return true if:
146
+ * 1. there is no directive
147
+ * 2. there is a directive named inlcude and the value is true
148
+ * 3. there is a directive named skip and the value is false
149
+ */
150
+ directiveCheck ( directive : DirectiveNode ) : boolean {
151
+ if ( directive ?. arguments ) {
152
+ // get the first argument
153
+ const argument = directive . arguments [ 0 ] ;
154
+ // ensure the argument name is 'if'
155
+ const argumentHasVariables =
156
+ argument . value . kind === Kind . VARIABLE && argument . name . value === 'if' ;
157
+ // access the value of the argument depending on whether it is passed as a variable or not
158
+ let directiveArgumentValue ;
159
+ if ( argument . value . kind === Kind . BOOLEAN ) {
160
+ directiveArgumentValue = Boolean ( argument . value . value ) ;
161
+ } else if ( argumentHasVariables ) {
162
+ directiveArgumentValue = Boolean ( this . variables [ argument . value . name . value ] ) ;
163
+ }
164
+
165
+ return (
166
+ ( directive . name . value === 'include' && directiveArgumentValue === true ) ||
167
+ ( directive . name . value === 'skip' && directiveArgumentValue === false )
168
+ ) ;
169
+ }
170
+ return true ;
171
+ }
172
+
173
+ private selectionNode ( node : SelectionNode , parentName : string ) : number {
135
174
let complexity = 0 ;
175
+ /**
176
+ * process this node if:
177
+ * 1. there is no directive
178
+ * 2. there is a directive named inlcude and the value is true
179
+ * 3. there is a directive named skip and the value is false
180
+ */
181
+ // const directive = node.directives;
182
+ // if (directive && this.directiveCheck(directive[0])) {
183
+ this . depth += 1 ;
184
+ if ( this . depth > this . maxDepth ) this . maxDepth = this . depth ;
136
185
// check the kind property against the set of selection nodes that are possible
137
186
if ( node . kind === Kind . FIELD ) {
138
187
// call the function that handle field nodes
139
188
complexity += this . fieldNode ( node , parentName . toLowerCase ( ) ) ;
140
189
} else if ( node . kind === Kind . FRAGMENT_SPREAD ) {
141
- complexity += this . fragmentCache [ node . name . value ] ;
190
+ // add complexity and depth from fragment cache
191
+ const { complexity : fragComplexity , depth : fragDepth } =
192
+ this . fragmentCache [ node . name . value ] ;
193
+ complexity += fragComplexity ;
194
+ this . depth += fragDepth ;
195
+ if ( this . depth > this . maxDepth ) this . maxDepth = this . depth ;
196
+ this . depth -= fragDepth ;
197
+
142
198
// This is a leaf
143
199
// need to parse fragment definition at root and get the result here
144
200
} else if ( node . kind === Kind . INLINE_FRAGMENT ) {
@@ -148,16 +204,21 @@ class ASTParser {
148
204
// If the TypeCondition is omitted, an inline fragment is considered to be of the same type as the enclosing context
149
205
const namedType = typeCondition ? typeCondition . name . value . toLowerCase ( ) : parentName ;
150
206
151
- // TODO: Handle directives like @include
207
+ // TODO: Handle directives like @include and @skip
208
+ // subtract 1 before, and add one after, entering the fragment selection to negate the additional level of depth added
209
+ this . depth -= 1 ;
152
210
complexity += this . selectionSetNode ( node . selectionSet , namedType ) ;
211
+ this . depth += 1 ;
153
212
} else {
154
- // FIXME: Consider removing this check. SelectionNodes cannot have any other kind in the current spec.
155
213
throw new Error ( `ERROR: ASTParser.selectionNode: node type not supported` ) ;
156
214
}
215
+
216
+ this . depth -= 1 ;
217
+ // }
157
218
return complexity ;
158
219
}
159
220
160
- selectionSetNode ( node : SelectionSetNode , parentName : string ) : number {
221
+ private selectionSetNode ( node : SelectionSetNode , parentName : string ) : number {
161
222
let complexity = 0 ;
162
223
let maxFragmentComplexity = 0 ;
163
224
// iterate shrough the 'selections' array on the seletion set node
@@ -185,7 +246,7 @@ class ASTParser {
185
246
return complexity + maxFragmentComplexity ;
186
247
}
187
248
188
- definitionNode ( node : DefinitionNode ) : number {
249
+ private definitionNode ( node : DefinitionNode ) : number {
189
250
let complexity = 0 ;
190
251
// check the kind property against the set of definiton nodes that are possible
191
252
if ( node . kind === Kind . OPERATION_DEFINITION ) {
@@ -207,25 +268,26 @@ class ASTParser {
207
268
// Duplicate fragment names are not allowed by the GraphQL spec and an error is thrown if used.
208
269
const fragmentName = node . name . value ;
209
270
210
- if ( this . fragmentCache [ fragmentName ] ) return this . fragmentCache [ fragmentName ] ;
211
-
212
271
const fragmentComplexity = this . selectionSetNode (
213
272
node . selectionSet ,
214
273
namedType . toLowerCase ( )
215
274
) ;
216
275
217
276
// Don't count fragment complexity in the node's complexity. Only when fragment is used.
218
- this . fragmentCache [ fragmentName ] = fragmentComplexity ;
219
- } else {
220
- // TODO: Verify that are no other type definition nodes that need to be handled (see ast.d.ts in 'graphql')
221
- // Other types include TypeSystemDefinitionNode (Schema, Type, Directvie) and
222
- // TypeSystemExtensionNode(Schema, Type);
223
- throw new Error ( `ERROR: ASTParser.definitionNode: ${ node . kind } type not supported` ) ;
224
- }
277
+ this . fragmentCache [ fragmentName ] = {
278
+ complexity : fragmentComplexity ,
279
+ depth : this . maxDepth - 1 , // subtract one from the calculated depth of the fragment to correct for the additional depth the fragment ads to the query when used
280
+ } ;
281
+ } // else {
282
+ // // TODO: Verify that are no other type definition nodes that need to be handled (see ast.d.ts in 'graphql')
283
+ // // Other types include TypeSystemDefinitionNode (Schema, Type, Directvie) and
284
+ // // TypeSystemExtensionNode(Schema, Type);
285
+ // throw new Error(`ERROR: ASTParser.definitionNode: ${node.kind} type not supported`);
286
+ // }
225
287
return complexity ;
226
288
}
227
289
228
- documentNode ( node : DocumentNode ) : number {
290
+ private documentNode ( node : DocumentNode ) : number {
229
291
let complexity = 0 ;
230
292
// sort the definitions array by kind so that fragments are always parsed first.
231
293
// Fragments must be parsed first so that their complexity is available to other nodes.
@@ -238,6 +300,10 @@ class ASTParser {
238
300
}
239
301
return complexity ;
240
302
}
303
+
304
+ processQuery ( queryAST : DocumentNode ) : number {
305
+ return this . documentNode ( queryAST ) ;
306
+ }
241
307
}
242
308
243
309
export default ASTParser ;
0 commit comments