Skip to content

Commit 7da8dae

Browse files
chirag04taion
authored andcommitted
allow formatErrorMessage callback (#3)
* 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
1 parent 9b9ed7c commit 7da8dae

File tree

6 files changed

+114
-5
lines changed

6 files changed

+114
-5
lines changed

.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"presets": [
33
["env", { "loose": true }]
4+
],
5+
"plugins": [
6+
"transform-object-rest-spread"
47
]
58
}

README.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const ComplexityLimitRule = createComplexityLimitRule(1000);
1313
// Then use this rule with validate().
1414
```
1515

16-
You can also provide custom costs for scalars and objects, and a custom cost factor for lists.
16+
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`.
1717

1818
```js
1919
const ComplexityLimitRule = createComplexityLimitRule(1000, {
@@ -23,6 +23,19 @@ const ComplexityLimitRule = createComplexityLimitRule(1000, {
2323
});
2424
```
2525

26+
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.
27+
28+
```js
29+
const ComplexityLimitRule = createComplexityLimitRule(1000, {
30+
onCost: (cost) => {
31+
console.log('query cost:', cost);
32+
},
33+
formatErrorMessage: cost => (
34+
`query with cost ${cost} exceeds complexity limit`
35+
),
36+
});
37+
```
38+
2639
[build-badge]: https://img.shields.io/travis/4Catalyzer/graphql-validation-complexity/master.svg
2740
[build]: https://travis-ci.org/4Catalyzer/graphql-validation-complexity
2841

package-lock.json

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"babel-cli": "^6.24.1",
4242
"babel-eslint": "^7.2.3",
4343
"babel-jest": "^20.0.3",
44+
"babel-plugin-transform-object-rest-spread": "^6.23.0",
4445
"babel-preset-env": "^1.5.2",
4546
"codecov": "^2.2.0",
4647
"eslint": "^3.19.0",

src/index.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,20 @@ export class ComplexityVisitor {
130130
}
131131
}
132132

133-
export function createComplexityLimitRule(maxCost, options = {}) {
133+
function complexityLimitExceededErrorMessage() {
134+
// By default, don't respond with the cost to avoid leaking information about
135+
// the cost scheme to a potentially malicious client.
136+
return 'query exceeds complexity limit';
137+
}
138+
139+
export function createComplexityLimitRule(
140+
maxCost,
141+
{
142+
onCost,
143+
formatErrorMessage = complexityLimitExceededErrorMessage,
144+
...options
145+
} = {},
146+
) {
134147
return function ComplexityLimit(context) {
135148
const visitor = new ComplexityVisitor(context, options);
136149

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

151164
if (node.kind === 'Document') {
152-
if (visitor.getCost() > maxCost) {
165+
const cost = visitor.getCost();
166+
167+
if (onCost) {
168+
onCost(cost);
169+
}
170+
171+
if (cost > maxCost) {
153172
context.reportError(new GraphQLError(
154-
'query exceeds complexity limit', [node],
173+
formatErrorMessage(cost), [node],
155174
));
156175
}
157176
}

test/createComplexityLimitRule.test.js

+62-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ describe('createComplexityLimitRule', () => {
1515
`);
1616

1717
const errors = validate(schema, ast, [createComplexityLimitRule(9)]);
18+
1819
expect(errors).toHaveLength(0);
1920
});
2021

21-
it('should not report error on an invalid query', () => {
22+
it('should report error on an invalid query', () => {
2223
const ast = parse(`
2324
query {
2425
list {
@@ -28,9 +29,69 @@ describe('createComplexityLimitRule', () => {
2829
`);
2930

3031
const errors = validate(schema, ast, [createComplexityLimitRule(9)]);
32+
3133
expect(errors).toHaveLength(1);
3234
expect(errors[0]).toMatchObject({
3335
message: 'query exceeds complexity limit',
3436
});
3537
});
38+
39+
it('should call onCost with complexity score', () => {
40+
const ast = parse(`
41+
query {
42+
item {
43+
name
44+
}
45+
}
46+
`);
47+
48+
const onCostSpy = jest.fn();
49+
50+
const errors = validate(schema, ast, [
51+
createComplexityLimitRule(9, { onCost: onCostSpy }),
52+
]);
53+
54+
expect(errors).toHaveLength(0);
55+
expect(onCostSpy).toHaveBeenCalledWith(1);
56+
});
57+
58+
it('should call onCost with cost when there are errors', () => {
59+
const ast = parse(`
60+
query {
61+
list {
62+
name
63+
}
64+
}
65+
`);
66+
67+
const onCostSpy = jest.fn();
68+
69+
const errors = validate(schema, ast, [
70+
createComplexityLimitRule(9, { onCost: onCostSpy }),
71+
]);
72+
73+
expect(errors).toHaveLength(1);
74+
expect(onCostSpy).toHaveBeenCalledWith(10);
75+
});
76+
77+
it('should call formatErrorMessage with cost', () => {
78+
const ast = parse(`
79+
query {
80+
list {
81+
name
82+
}
83+
}
84+
`);
85+
86+
const errors = validate(schema, ast, [
87+
createComplexityLimitRule(9, {
88+
formatErrorMessage: cost => `custom error, cost ${cost}`,
89+
}),
90+
]);
91+
92+
expect(errors).toHaveLength(1);
93+
expect(errors[0]).toMatchObject({
94+
message: 'custom error, cost 10',
95+
});
96+
});
3697
});

0 commit comments

Comments
 (0)