diff --git a/.circleci/config.yml b/.circleci/config.yml index 2dbb463..f59e3ec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ workflows: # definitions for field extensions in older @types/graphql versions matrix: parameters: - graphql-version: ["~0.13", "~14.0", "~14.5", "~14.6", "~15.0"] + graphql-version: ['~14.5', '~14.6', '~15.0'] - test-and-build: # Leave graphql-version unspecified to respect the lockfile and also run tsc name: test-and-build-with-typecheck @@ -22,7 +22,7 @@ jobs: parameters: graphql-version: type: string - default: "" + default: '' docker: # specify the version you desire here @@ -36,9 +36,9 @@ jobs: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "package.json" }}-<< parameters.graphql-version >> - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - v1-dependencies-{{ checksum "package.json" }}-<< parameters.graphql-version >> + # fallback to using the latest cache if no exact match is found + - v1-dependencies- - when: condition: << parameters.graphql-version >> diff --git a/.gitignore b/.gitignore index b3e635a..f76326d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .DS_Store .idea +.vscode diff --git a/README.md b/README.md index 65eb36b..0d629d3 100644 --- a/README.md +++ b/README.md @@ -171,16 +171,21 @@ const query = parse(` } `); -const complexity = getComplexity({ - estimators: [simpleEstimator({ defaultComplexity: 1 })], - schema, - query, - variables: { - count: 10, - }, -}); - -console.log(complexity); // Output: 3 +try { + const complexity = getComplexity({ + estimators: [simpleEstimator({ defaultComplexity: 1 })], + schema, + query, + variables: { + count: 10, + }, + }); + + console.log(complexity); // Output: 3 +} catch (e) { + // Log error in case complexity cannot be calculated (invalid query, misconfiguration, etc.) + console.error('Could not calculate complexity', e.message); +} ``` ## Prior Art diff --git a/package.json b/package.json index 5e86a21..c6efcfe 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lodash.get": "^4.4.2" }, "peerDependencies": { - "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0" + "graphql": "^14.5.0 || ^15.0.0" }, "files": [ "dist", diff --git a/src/QueryComplexity.ts b/src/QueryComplexity.ts index 95441c4..410655b 100644 --- a/src/QueryComplexity.ts +++ b/src/QueryComplexity.ts @@ -96,11 +96,12 @@ export function getComplexity(options: { }): number { const typeInfo = new TypeInfo(options.schema); + const errors: GraphQLError[] = []; const context = new ValidationContext( options.schema, options.query, typeInfo, - () => null + (error) => errors.push(error) ); const visitor = new QueryComplexity(context, { // Maximum complexity does not matter since we're only interested in the calculated complexity. @@ -111,6 +112,12 @@ export function getComplexity(options: { }); visit(options.query, visitWithTypeInfo(typeInfo, visitor)); + + // Throw first error if any + if (errors.length) { + throw errors.pop(); + } + return visitor.complexity; } @@ -244,7 +251,7 @@ export default class QueryComplexity { ( complexities: ComplexityMap, childNode: FieldNode | FragmentSpreadNode | InlineFragmentNode - ) => { + ): ComplexityMap => { // let nodeComplexity = 0; let innerComplexities = complexities; @@ -297,7 +304,8 @@ export default class QueryComplexity { this.variableValues || {} ); } catch (e) { - return this.context.reportError(e); + this.context.reportError(e); + return complexities; } // Check if we have child complexity diff --git a/src/__tests__/QueryComplexity-test.ts b/src/__tests__/QueryComplexity-test.ts index 5d62af8..1b28628 100644 --- a/src/__tests__/QueryComplexity-test.ts +++ b/src/__tests__/QueryComplexity-test.ts @@ -2,7 +2,14 @@ * Created by Ivo Meißner on 28.07.17. */ -import { parse, TypeInfo, visit, visitWithTypeInfo } from 'graphql'; +import { + parse, + TypeInfo, + visit, + visitWithTypeInfo, + validate, + specifiedRules, +} from 'graphql'; import { expect } from 'chai'; @@ -523,7 +530,7 @@ describe('QueryComplexity analysis', () => { ); }); - it('should return NaN when no astNode available on field when using directiveEstimator', () => { + it('should throw error when no astNode available on field when using directiveEstimator', () => { const ast = parse(` query { _service { @@ -532,12 +539,13 @@ describe('QueryComplexity analysis', () => { } `); - const complexity = getComplexity({ - estimators: [directiveEstimator()], - schema, - query: ast, - }); - expect(Number.isNaN(complexity)).to.equal(true); + expect(() => { + getComplexity({ + estimators: [directiveEstimator()], + schema, + query: ast, + }); + }).to.throw(/No complexity could be calculated for field Query._service/); }); it('should skip complexity calculation by directiveEstimator when no astNode available on field', () => { @@ -796,4 +804,32 @@ describe('QueryComplexity analysis', () => { }); expect(complexity).to.equal(30); // 3 fields on nonNullItem * 10 }); + + it('should handle invalid argument values for multiple query fields', () => { + const ast = parse(` + query { + requiredArgs(count: x) { + scalar + complexScalar + } + nonNullItem { + scalar + complexScalar + variableScalar(count: 10) + } + } + `); + + validate(schema, ast, [ + ...specifiedRules, + createComplexityRule({ + maximumComplexity: 1000, + estimators: [ + simpleEstimator({ + defaultComplexity: 1, + }), + ], + }), + ]); + }); });