Skip to content

Commit

Permalink
allow formatErrorMessage callback (#3)
Browse files Browse the repository at this point in the history
* allow onCost callback for logging

* formatErrorMessage

* include cost in default message

* fix test

* bring onCost back

* update readme

* address feedback

* update package-lock

* Clean things up a bit
  • Loading branch information
chirag04 authored and taion committed Jun 27, 2017
1 parent 9b9ed7c commit 7da8dae
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"presets": [
["env", { "loose": true }]
],
"plugins": [
"transform-object-rest-spread"
]
}
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ComplexityLimitRule = createComplexityLimitRule(1000);
// Then use this rule with validate().
```

You can also provide custom costs for scalars and objects, and a custom cost factor for lists.
You can provide a configuration object with custom costs for scalars and objects as `scalarCost` and `objectCost` respectively, and a custom cost factor for lists as `listFactor`.

```js
const ComplexityLimitRule = createComplexityLimitRule(1000, {
Expand All @@ -23,6 +23,19 @@ const ComplexityLimitRule = createComplexityLimitRule(1000, {
});
```

The configuration object also supports an `onCost` callback for logging query costs and a `formatErrorMessage` callback for customizing error messages. `onCost` will be called for every query with its cost. `formatErrorMessage` will be called with the cost whenever a query exceeds the complexity limit, and should return a string containing the error message.

```js
const ComplexityLimitRule = createComplexityLimitRule(1000, {
onCost: (cost) => {
console.log('query cost:', cost);
},
formatErrorMessage: cost => (
`query with cost ${cost} exceeds complexity limit`
),
});
```

[build-badge]: https://img.shields.io/travis/4Catalyzer/graphql-validation-complexity/master.svg
[build]: https://travis-ci.org/4Catalyzer/graphql-validation-complexity

Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"babel-cli": "^6.24.1",
"babel-eslint": "^7.2.3",
"babel-jest": "^20.0.3",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-env": "^1.5.2",
"codecov": "^2.2.0",
"eslint": "^3.19.0",
Expand Down
25 changes: 22 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,20 @@ export class ComplexityVisitor {
}
}

export function createComplexityLimitRule(maxCost, options = {}) {
function complexityLimitExceededErrorMessage() {
// By default, don't respond with the cost to avoid leaking information about
// the cost scheme to a potentially malicious client.
return 'query exceeds complexity limit';
}

export function createComplexityLimitRule(
maxCost,
{
onCost,
formatErrorMessage = complexityLimitExceededErrorMessage,
...options
} = {},
) {
return function ComplexityLimit(context) {
const visitor = new ComplexityVisitor(context, options);

Expand All @@ -149,9 +162,15 @@ export function createComplexityLimitRule(maxCost, options = {}) {
}

if (node.kind === 'Document') {
if (visitor.getCost() > maxCost) {
const cost = visitor.getCost();

if (onCost) {
onCost(cost);
}

if (cost > maxCost) {
context.reportError(new GraphQLError(
'query exceeds complexity limit', [node],
formatErrorMessage(cost), [node],
));
}
}
Expand Down
63 changes: 62 additions & 1 deletion test/createComplexityLimitRule.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ describe('createComplexityLimitRule', () => {
`);

const errors = validate(schema, ast, [createComplexityLimitRule(9)]);

expect(errors).toHaveLength(0);
});

it('should not report error on an invalid query', () => {
it('should report error on an invalid query', () => {
const ast = parse(`
query {
list {
Expand All @@ -28,9 +29,69 @@ describe('createComplexityLimitRule', () => {
`);

const errors = validate(schema, ast, [createComplexityLimitRule(9)]);

expect(errors).toHaveLength(1);
expect(errors[0]).toMatchObject({
message: 'query exceeds complexity limit',
});
});

it('should call onCost with complexity score', () => {
const ast = parse(`
query {
item {
name
}
}
`);

const onCostSpy = jest.fn();

const errors = validate(schema, ast, [
createComplexityLimitRule(9, { onCost: onCostSpy }),
]);

expect(errors).toHaveLength(0);
expect(onCostSpy).toHaveBeenCalledWith(1);
});

it('should call onCost with cost when there are errors', () => {
const ast = parse(`
query {
list {
name
}
}
`);

const onCostSpy = jest.fn();

const errors = validate(schema, ast, [
createComplexityLimitRule(9, { onCost: onCostSpy }),
]);

expect(errors).toHaveLength(1);
expect(onCostSpy).toHaveBeenCalledWith(10);
});

it('should call formatErrorMessage with cost', () => {
const ast = parse(`
query {
list {
name
}
}
`);

const errors = validate(schema, ast, [
createComplexityLimitRule(9, {
formatErrorMessage: cost => `custom error, cost ${cost}`,
}),
]);

expect(errors).toHaveLength(1);
expect(errors[0]).toMatchObject({
message: 'custom error, cost 10',
});
});
});

0 comments on commit 7da8dae

Please sign in to comment.