44 SelectionSetNode ,
55 DefinitionNode ,
66 Kind ,
7+ DirectiveNode ,
78 SelectionNode ,
9+ getArgumentValues ,
810} from 'graphql' ;
911import { FieldWeight , TypeWeightObject , Variables } from '../@types/buildTypeWeights' ;
1012/**
@@ -30,14 +32,20 @@ import { FieldWeight, TypeWeightObject, Variables } from '../@types/buildTypeWei
3032class ASTParser {
3133 typeWeights : TypeWeightObject ;
3234
35+ depth : number ;
36+ 37+ maxDepth : number ;
38+ 3339 variables : Variables ;
3440
35- fragmentCache : { [ index : string ] : number } ;
41+ fragmentCache : { [ index : string ] : { complexity : number ; depth : number } } ;
3642
3743 constructor ( typeWeights : TypeWeightObject , variables : Variables ) {
3844 this . typeWeights = typeWeights ;
3945 this . variables = variables ;
4046 this . fragmentCache = { } ;
47+ this . depth = 0 ;
48+ this . maxDepth = 0 ;
4149 }
4250
4351 private calculateCost (
@@ -59,6 +67,8 @@ class ASTParser {
5967 if ( node . arguments && typeof typeWeight === 'function' ) {
6068 // FIXME: May never happen but what if weight is a function and arguments don't exist
6169 calculatedWeight += typeWeight ( [ ...node . arguments ] , this . variables , selectionsCost ) ;
70+ } else if ( typeof typeWeight === 'number' ) {
71+ calculatedWeight += typeWeight + selectionsCost ;
6272 } else {
6373 calculatedWeight += this . typeWeights [ typeName ] . weight + selectionsCost ;
6474 }
@@ -67,7 +77,7 @@ class ASTParser {
6777 return complexity ;
6878 }
6979
70- fieldNode ( node : FieldNode , parentName : string ) : number {
80+ private fieldNode ( node : FieldNode , parentName : string ) : number {
7181 try {
7282 let complexity = 0 ;
7383 const parentType = this . typeWeights [ parentName ] ;
@@ -78,7 +88,7 @@ class ASTParser {
7888 }
7989 let typeName : string | undefined ;
8090 let typeWeight : FieldWeight | undefined ;
81- 91+ if ( node . name . value === '__typename' ) return complexity ;
8292 if ( node . name . value in this . typeWeights ) {
8393 // node is an object type n the typeWeight root
8494 typeName = node . name . value ;
@@ -131,14 +141,60 @@ class ASTParser {
131141 }
132142 }
133143
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 {
135174 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 ;
136185 // check the kind property against the set of selection nodes that are possible
137186 if ( node . kind === Kind . FIELD ) {
138187 // call the function that handle field nodes
139188 complexity += this . fieldNode ( node , parentName . toLowerCase ( ) ) ;
140189 } 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+ 142198 // This is a leaf
143199 // need to parse fragment definition at root and get the result here
144200 } else if ( node . kind === Kind . INLINE_FRAGMENT ) {
@@ -148,16 +204,21 @@ class ASTParser {
148204 // If the TypeCondition is omitted, an inline fragment is considered to be of the same type as the enclosing context
149205 const namedType = typeCondition ? typeCondition . name . value . toLowerCase ( ) : parentName ;
150206
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 ;
152210 complexity += this . selectionSetNode ( node . selectionSet , namedType ) ;
211+ this . depth += 1 ;
153212 } else {
154- // FIXME: Consider removing this check. SelectionNodes cannot have any other kind in the current spec.
155213 throw new Error ( `ERROR: ASTParser.selectionNode: node type not supported` ) ;
156214 }
215+ 216+ this . depth -= 1 ;
217+ // }
157218 return complexity ;
158219 }
159220
160- selectionSetNode ( node : SelectionSetNode , parentName : string ) : number {
221+ private selectionSetNode ( node : SelectionSetNode , parentName : string ) : number {
161222 let complexity = 0 ;
162223 let maxFragmentComplexity = 0 ;
163224 // iterate shrough the 'selections' array on the seletion set node
@@ -185,7 +246,7 @@ class ASTParser {
185246 return complexity + maxFragmentComplexity ;
186247 }
187248
188- definitionNode ( node : DefinitionNode ) : number {
249+ private definitionNode ( node : DefinitionNode ) : number {
189250 let complexity = 0 ;
190251 // check the kind property against the set of definiton nodes that are possible
191252 if ( node . kind === Kind . OPERATION_DEFINITION ) {
@@ -207,25 +268,26 @@ class ASTParser {
207268 // Duplicate fragment names are not allowed by the GraphQL spec and an error is thrown if used.
208269 const fragmentName = node . name . value ;
209270
210- if ( this . fragmentCache [ fragmentName ] ) return this . fragmentCache [ fragmentName ] ;
211- 212271 const fragmentComplexity = this . selectionSetNode (
213272 node . selectionSet ,
214273 namedType . toLowerCase ( )
215274 ) ;
216275
217276 // 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+ // }
225287 return complexity ;
226288 }
227289
228- documentNode ( node : DocumentNode ) : number {
290+ private documentNode ( node : DocumentNode ) : number {
229291 let complexity = 0 ;
230292 // sort the definitions array by kind so that fragments are always parsed first.
231293 // Fragments must be parsed first so that their complexity is available to other nodes.
@@ -238,6 +300,10 @@ class ASTParser {
238300 }
239301 return complexity ;
240302 }
303+ 304+ processQuery ( queryAST : DocumentNode ) : number {
305+ return this . documentNode ( queryAST ) ;
306+ }
241307}
242308
243309export default ASTParser ;
0 commit comments