Skip to content

Commit e44b748

Browse files
author
Vincent Smith
committed
Ignore Missing Ignore/Skip Directives
Currently unused variables are ignored for the purposes of calculating complexity. However, because graphql-js's `getVariableValues` method returns either a map of coersed values, OR an array of errors, if directives are present in conjuection with unused variables then strange errors will be raised. In particular errors will state that directive variables were not present when they may well have been. This change adds fallback measures to ensure that complexity will stil be calculated if unused variables are present alonside directives. Fixes slicknode#69
1 parent d59c636 commit e44b748

File tree

2 files changed

+124
-23
lines changed

2 files changed

+124
-23
lines changed

src/QueryComplexity.ts

+34-20
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ export default class QueryComplexity {
173173
this.options.variables ?? {}
174174
).coerced;
175175

176+
// If there are no coerced variable values it is because one or all of those variables were
177+
// absent. In this case fallback to what was provided and ignore defaults.
178+
if (!this.variableValues) {
179+
this.variableValues = this.options.variables;
180+
}
181+
176182
switch (operation.operation) {
177183
case 'query':
178184
this.complexity += this.nodeComplexity(
@@ -261,28 +267,36 @@ export default class QueryComplexity {
261267

262268
for (const directive of childNode.directives ?? []) {
263269
const directiveName = directive.name.value;
264-
switch (directiveName) {
265-
case 'include': {
266-
const values = getDirectiveValues(
267-
this.includeDirectiveDef,
268-
childNode,
269-
this.variableValues || {}
270-
);
271-
if (typeof values.if === 'boolean') {
272-
includeNode = values.if;
270+
try {
271+
switch (directiveName) {
272+
case 'include': {
273+
const values = getDirectiveValues(
274+
this.includeDirectiveDef,
275+
childNode,
276+
this.variableValues || {}
277+
);
278+
if (typeof values.if === 'boolean') {
279+
includeNode = values.if;
280+
}
281+
break;
273282
}
274-
break;
275-
}
276-
case 'skip': {
277-
const values = getDirectiveValues(
278-
this.skipDirectiveDef,
279-
childNode,
280-
this.variableValues || {}
281-
);
282-
if (typeof values.if === 'boolean') {
283-
skipNode = values.if;
283+
case 'skip': {
284+
const values = getDirectiveValues(
285+
this.skipDirectiveDef,
286+
childNode,
287+
this.variableValues || {}
288+
);
289+
if (typeof values.if === 'boolean') {
290+
skipNode = values.if;
291+
}
292+
break;
284293
}
285-
break;
294+
}
295+
} catch (error) {
296+
// Ignore GraphQLErrors in favor of falling back to the default values for
297+
// includeNode and skipNode
298+
if (!(error instanceof GraphQLError)) {
299+
throw error;
286300
}
287301
}
288302
}

src/__tests__/QueryComplexity-test.ts

+90-3
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,93 @@ describe('QueryComplexity analysis', () => {
271271
expect(visitor.complexity).to.equal(10);
272272
});
273273

274+
it('should ignore unused variables with include', () => {
275+
const ast = parse(`
276+
query ($unusedVar: ID!, $shouldInclude: Boolean! = false) {
277+
variableScalar(count: 100) @include(if: $shouldInclude)
278+
}
279+
`);
280+
281+
const context = new CompatibleValidationContext(schema, ast, typeInfo);
282+
const visitor = new ComplexityVisitor(context, {
283+
maximumComplexity: 100,
284+
estimators: [simpleEstimator({ defaultComplexity: 10 })],
285+
variables: { shouldInclude: true },
286+
});
287+
288+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
289+
expect(visitor.complexity).to.equal(10);
290+
});
291+
292+
it('should ignore unused variables with skip', () => {
293+
const ast = parse(`
294+
query ($unusedVar: ID!, $shouldSkip: Boolean! = false) {
295+
variableScalar(count: 100) @skip(if: $shouldSkip)
296+
}
297+
`);
298+
299+
const context = new CompatibleValidationContext(schema, ast, typeInfo);
300+
const visitor = new ComplexityVisitor(context, {
301+
maximumComplexity: 100,
302+
estimators: [simpleEstimator({ defaultComplexity: 10 })],
303+
variables: { shouldSkip: false },
304+
});
305+
306+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
307+
expect(visitor.complexity).to.equal(10);
308+
});
309+
310+
it('should ignore unused variables with unused include', () => {
311+
const ast = parse(`
312+
query ($unusedVar: ID!, $shouldInclude: Boolean! = false) {
313+
variableScalar(count: 100) @include(if: $shouldInclude)
314+
}
315+
`);
316+
317+
const context = new CompatibleValidationContext(schema, ast, typeInfo);
318+
const visitor = new ComplexityVisitor(context, {
319+
maximumComplexity: 100,
320+
estimators: [simpleEstimator({ defaultComplexity: 10 })],
321+
});
322+
323+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
324+
expect(visitor.complexity).to.equal(10);
325+
});
326+
327+
it('should ignore unused variables with unused skip', () => {
328+
const ast = parse(`
329+
query ($unusedVar: ID!, $shouldSkip: Boolean! = false) {
330+
variableScalar(count: 100) @skip(if: $shouldSkip)
331+
}
332+
`);
333+
334+
const context = new CompatibleValidationContext(schema, ast, typeInfo);
335+
const visitor = new ComplexityVisitor(context, {
336+
maximumComplexity: 100,
337+
estimators: [simpleEstimator({ defaultComplexity: 10 })],
338+
});
339+
340+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
341+
expect(visitor.complexity).to.equal(10);
342+
});
343+
344+
it('should ignore multiple unused variables', () => {
345+
const ast = parse(`
346+
query ($unusedVar: ID!, $anotherUnusedVar: Boolean!) {
347+
variableScalar(count: 100)
348+
}
349+
`);
350+
351+
const context = new CompatibleValidationContext(schema, ast, typeInfo);
352+
const visitor = new ComplexityVisitor(context, {
353+
maximumComplexity: 100,
354+
estimators: [simpleEstimator({ defaultComplexity: 10 })],
355+
});
356+
357+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
358+
expect(visitor.complexity).to.equal(10);
359+
});
360+
274361
it('should ignore unknown field', () => {
275362
const ast = parse(`
276363
query {
@@ -639,7 +726,7 @@ describe('QueryComplexity analysis', () => {
639726
...on Union {
640727
...on Item {
641728
complexScalar1: complexScalar
642-
}
729+
}
643730
}
644731
...on SecondItem {
645732
scalar
@@ -678,7 +765,7 @@ describe('QueryComplexity analysis', () => {
678765
fragment F on Union {
679766
...on Item {
680767
complexScalar1: complexScalar
681-
}
768+
}
682769
}
683770
`);
684771

@@ -759,7 +846,7 @@ describe('QueryComplexity analysis', () => {
759846
}
760847
}
761848
}
762-
849+
763850
fragment F on Query {
764851
complexScalar
765852
...on Query {

0 commit comments

Comments
 (0)