Skip to content

Commit 04334d2

Browse files
committed
Added multipliers to directiveEstimator, README updates #3
1 parent 274709a commit 04334d2

File tree

8 files changed

+222
-10
lines changed

8 files changed

+222
-10
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ or write your own:
7272

7373
* **[`simpleEstimator`](src/estimators/simple/README.md):** The simple estimator returns a fixed complexity for each field. Can be used as
7474
last estimator in the chain for a default value.
75-
* **[`fieldConfigEstimator`](src/estimators/simple/README.md):** The field config estimator lets you set a numeric value or a custom estimator
76-
function in the field config of your schema.
7775
* **[`directiveEstimator`](src/estimators/directive/README.md):** Set the complexity via a directive in your
7876
schema definition (for example via GraphQL SDL)
77+
* **[`fieldConfigEstimator`](src/estimators/simple/README.md):** The field config estimator lets you set a numeric value or a custom estimator
78+
function in the field config of your schema.
7979
* **[`legacyEstimator`](src/estimators/legacy/README.md):** The legacy estimator implements the logic of previous versions. Can be used
8080
to gradually migrate your codebase to new estimators.
81-
* PR welcome...
81+
* PRs welcome...
8282

8383
Consult the documentation of each estimator for information about how to use them.
8484

@@ -131,4 +131,4 @@ app.use('/api', graphqlHTTP(async (request, response, {variables}) => ({
131131
This project is inspired by the following prior projects:
132132

133133
- Query complexity analysis in the [Sangria GraphQL](http://sangria-graphql.org/) implementation.
134-
134+
- [graphql-cost-analysis](https://github.com/pa-bru/graphql-cost-analysis) - Multipliers and directiveEstimator

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"directories": {
1717
"lib": "./dist"
1818
},
19-
"dependencies": {},
19+
"dependencies": {
20+
"lodash.get": "^4.4.2"
21+
},
2022
"peerDependencies": {
2123
"graphql": "^0.13.2"
2224
},
@@ -25,7 +27,7 @@
2527
"README.md",
2628
"LICENSE"
2729
],
28-
"repository": "ivome/graphql-query-complexity",
30+
"repository": "slicknode/graphql-query-complexity",
2931
"keywords": [
3032
"graphql",
3133
"query complexity"
@@ -36,6 +38,7 @@
3638
"@types/assert": "^0.0.31",
3739
"@types/chai": "^4.1.4",
3840
"@types/graphql": "^0.13.4",
41+
"@types/lodash.get": "^4.4.4",
3942
"@types/mocha": "^5.2.5",
4043
"chai": "^4.1.0",
4144
"eslint": "^5.4.0",

src/estimators/directive/README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,31 @@ Define your schema and add the complexity directive:
2626

2727
```graphql
2828
type Query {
29-
# Set the complexity values on the fields via the directive
29+
# Fixed complexity of 5
3030
someField: String @complexity(value: 5)
31+
32+
# Multiply the complexity of the field with a numeric input value
33+
# If limit=2 this would result in a complexity of 4
34+
listScalar(limit: Int): String @complexity(value: 2, multipliers: ["limit"])
35+
36+
# Use a multiplier that is nested in a by defining the multiplier with path notation (see library lodash.get)
37+
multiLevelMultiplier(filter: Filter): String @complexity(value: 1, multipliers: ["filter.limit"])
38+
39+
# If the multiplier is an array, it uses the array length as multiplier
40+
arrayLength(ids: [ID]): String @complexity(value: 1, multipliers: ["ids"])
41+
42+
# Using multipliers on fields with composite types calculates the complexity as follows:
43+
# (value + childComplexity) * multiplier1 [... * multiplier2]
44+
compositeTypes(ids: [ID]): ChildType @complexity(value: 2, multipliers: ["ids"])
45+
}
46+
47+
type ChildType {
48+
a: String @complexity(value: 1)
49+
}
50+
51+
input Filter {
52+
limit: Int
3153
}
3254
```
55+
56+
The multipliers can be combined. Configured multipliers that don't have a value or `NULL` are ignored.

src/estimators/directive/__tests__/directiveEstimator-test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,130 @@ describe('directiveEstimator analysis', () => {
9797
visit(ast, visitWithTypeInfo(typeInfo, visitor));
9898
expect(visitor.complexity).to.equal(1);
9999
});
100+
101+
it('returns value + child complexity for configured multipliers but no values', () => {
102+
const ast = parse(`
103+
query {
104+
childList {
105+
scalar
106+
}
107+
}
108+
`);
109+
110+
const context = new ValidationContext(schema, ast, typeInfo);
111+
const visitor = new ComplexityVisitor(context, {
112+
maximumComplexity: 100,
113+
estimators: [
114+
directiveEstimator()
115+
]
116+
});
117+
118+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
119+
expect(visitor.complexity).to.equal(5);
120+
});
121+
122+
it('uses numeric multiplier value', () => {
123+
const ast = parse(`
124+
query {
125+
childList(limit: 2) {
126+
scalar
127+
}
128+
}
129+
`);
130+
131+
const context = new ValidationContext(schema, ast, typeInfo);
132+
const visitor = new ComplexityVisitor(context, {
133+
maximumComplexity: 100,
134+
estimators: [
135+
directiveEstimator()
136+
]
137+
});
138+
139+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
140+
expect(visitor.complexity).to.equal(10);
141+
});
142+
143+
it('combines multiple numeric multiplier values', () => {
144+
const ast = parse(`
145+
query {
146+
childList(limit: 2, first: 2) {
147+
scalar
148+
}
149+
}
150+
`);
151+
152+
const context = new ValidationContext(schema, ast, typeInfo);
153+
const visitor = new ComplexityVisitor(context, {
154+
maximumComplexity: 100,
155+
estimators: [
156+
directiveEstimator()
157+
]
158+
});
159+
160+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
161+
expect(visitor.complexity).to.equal(20);
162+
});
163+
164+
it('uses multiplier array value length', () => {
165+
const ast = parse(`
166+
query {
167+
childList(ids: ["a", "b"]) {
168+
scalar
169+
}
170+
}
171+
`);
172+
173+
const context = new ValidationContext(schema, ast, typeInfo);
174+
const visitor = new ComplexityVisitor(context, {
175+
maximumComplexity: 100,
176+
estimators: [
177+
directiveEstimator()
178+
]
179+
});
180+
181+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
182+
expect(visitor.complexity).to.equal(10);
183+
});
184+
185+
it('uses nested multiplier paths', () => {
186+
const ast = parse(`
187+
query {
188+
childList(filter: {limit: 3}) {
189+
scalar
190+
}
191+
}
192+
`);
193+
194+
const context = new ValidationContext(schema, ast, typeInfo);
195+
const visitor = new ComplexityVisitor(context, {
196+
maximumComplexity: 100,
197+
estimators: [
198+
directiveEstimator()
199+
]
200+
});
201+
202+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
203+
expect(visitor.complexity).to.equal(15);
204+
});
205+
206+
it('uses multi level nested multiplier paths with array reference', () => {
207+
const ast = parse(`
208+
query {
209+
childList(filter: {filters: [{limit: 2}]}) {
210+
scalar
211+
}
212+
}
213+
`);
214+
215+
const context = new ValidationContext(schema, ast, typeInfo);
216+
const visitor = new ComplexityVisitor(context, {
217+
maximumComplexity: 100,
218+
estimators: [
219+
directiveEstimator()
220+
]
221+
});
222+
223+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
224+
expect(visitor.complexity).to.equal(10);
225+
});
100226
});

src/estimators/directive/__tests__/fixtures/schema.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,25 @@ type Query {
1111
scalar: String @complexity(value: 5)
1212
negativeCostScalar: String @complexity(value: -20)
1313
multiDirective: String @cost(value: 1) @complexity(value: 2)
14+
15+
childList(
16+
limit: Int,
17+
ids: [ID],
18+
first: Int,
19+
filter: Filter
20+
): [ChildType] @complexity(
21+
value: 3,
22+
multipliers: ["first", "limit", "ids", "filter.limit", "filter.filters.0.limit"]
23+
)
24+
}
25+
26+
input Filter {
27+
limit: Int
28+
ids: [ID]
29+
filters: [Filter]
30+
}
31+
32+
type ChildType {
33+
scalar: String @complexity(value: 2)
1434
}
1535
`);

src/estimators/directive/index.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {ComplexityEstimator, ComplexityEstimatorArgs} from '../../QueryComplexit
22
import {getDirectiveValues, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString} from 'graphql';
33
import { GraphQLDirective } from 'graphql/type/directives';
44
import { DirectiveLocation } from 'graphql/language/directiveLocation';
5+
import get from 'lodash.get';
56

67
export default function (options?: {}): ComplexityEstimator {
78
const mergedOptions = {
@@ -28,6 +29,23 @@ export default function (options?: {}): ComplexityEstimator {
2829

2930
return (args: ComplexityEstimatorArgs) => {
3031
const values = getDirectiveValues(directive, args.field.astNode);
31-
return values.value + args.childComplexity;
32+
33+
// Get multipliers
34+
let totalMultiplier = 1;
35+
if (values.multipliers) {
36+
totalMultiplier = values.multipliers.reduce((aggregated: number, multiplier: string) => {
37+
const multiplierValue = get(args.args, multiplier);
38+
39+
if (typeof multiplierValue === 'number') {
40+
return aggregated * multiplierValue;
41+
}
42+
if (Array.isArray(multiplierValue)) {
43+
return aggregated * multiplierValue.length;
44+
}
45+
return aggregated;
46+
}, totalMultiplier);
47+
}
48+
49+
return (values.value + args.childComplexity) * totalMultiplier;
3250
};
3351
}

src/estimators/fieldConfig/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ the estimator does not return any value and the next estimator in the chain is e
77
## Usage
88

99
```typescript
10-
import queryComplexity, {fieldConfigEstimator} from 'graphql-query-complexity';
10+
import queryComplexity, {
11+
fieldConfigEstimator,
12+
simpleEstimator
13+
} from 'graphql-query-complexity';
1114

1215
const rule = queryComplexity({
1316
estimators: [
14-
fieldConfigEstimator()
17+
fieldConfigEstimator(),
18+
19+
// We use the simpleEstimator as fallback so we only need to
20+
// define the complexity for non 1 values (this is not required...)
21+
simpleEstimator({defaultComplexity: 1})
1522
]
1623
// ... other config
1724
});

yarn.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
version "0.13.4"
1515
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.4.tgz#55ae9c29f0fd6b85ee536f5c72b4769d5c5e06b1"
1616

17+
"@types/lodash.get@^4.4.4":
18+
version "4.4.4"
19+
resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.4.tgz#34b67841594e4ddc8853341d65e971a38cb4e2f0"
20+
dependencies:
21+
"@types/lodash" "*"
22+
23+
"@types/lodash@*":
24+
version "4.14.116"
25+
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9"
26+
1727
"@types/mocha@^5.2.5":
1828
version "5.2.5"
1929
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073"
@@ -553,6 +563,10 @@ levn@^0.3.0, levn@~0.3.0:
553563
prelude-ls "~1.1.2"
554564
type-check "~0.3.2"
555565

566+
lodash.get@^4.4.2:
567+
version "4.4.2"
568+
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
569+
556570
557571
version "4.0.1"
558572
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"

0 commit comments

Comments
 (0)