Skip to content

Commit 274709a

Browse files
committed
Added draft for directiveEstimator #3
1 parent 484473d commit 274709a

File tree

7 files changed

+186
-61
lines changed

7 files changed

+186
-61
lines changed

README.md

Lines changed: 3 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ or write your own:
7474
last estimator in the chain for a default value.
7575
* **[`fieldConfigEstimator`](src/estimators/simple/README.md):** The field config estimator lets you set a numeric value or a custom estimator
7676
function in the field config of your schema.
77+
* **[`directiveEstimator`](src/estimators/directive/README.md):** Set the complexity via a directive in your
78+
schema definition (for example via GraphQL SDL)
7779
* **[`legacyEstimator`](src/estimators/legacy/README.md):** The legacy estimator implements the logic of previous versions. Can be used
7880
to gradually migrate your codebase to new estimators.
7981
* PR welcome...
8082

83+
Consult the documentation of each estimator for information about how to use them.
8184

8285
## Creating Custom Estimators
8386

@@ -101,65 +104,6 @@ type ComplexityEstimatorArgs = {
101104
type ComplexityEstimator = (options: ComplexityEstimatorArgs) => number | void;
102105
```
103106

104-
105-
## Customizing complexity calculation
106-
107-
By default, every field gets a complexity of 1. Let's look at the following example query:
108-
109-
```graphql
110-
query {
111-
posts(count: 10) {
112-
title
113-
text
114-
}
115-
}
116-
```
117-
118-
This would result in a complexity of 3. The fields `posts`, `title` and `text` each add a complexity of 1.
119-
If we assume that the posts field returns a list of 10 posts, the complexity estimation is pretty inaccurate.
120-
121-
When defining your fields, you have a two options to customize the calculation.
122-
123-
You can set a custom complexity in the field config:
124-
125-
```javascript
126-
const Post = new GraphQLObjectType({
127-
name: 'Post',
128-
fields: () => ({
129-
title: { type: GraphQLString },
130-
text: { type: GraphQLString, complexity: 5 },
131-
}),
132-
});
133-
```
134-
The same query would now result in a complexity of 7.
135-
5 for the `text` field and 1 for each of the other fields.
136-
137-
You can also pass a calculation function in the field config to determine a custom complexity.
138-
This function will provide the complexity of the child nodes as well as the field input arguments.
139-
140-
That way you can make a more realistic estimation of individual field complexity values:
141-
142-
```javascript
143-
const Query = new GraphQLObjectType({
144-
name: 'Query',
145-
fields: () => ({
146-
posts: {
147-
type: new GraphQLList(Post),
148-
complexity: (args, childComplexity) => childComplexity * args.count,
149-
args: {
150-
count: {
151-
type: GraphQLInt,
152-
defaultValue: 10
153-
}
154-
}
155-
},
156-
}),
157-
});
158-
```
159-
160-
This would result in a complexity of 60 since the `childComplexity` of posts (`text` 5, `title` 1) is multiplied by the
161-
number of posts (`args.count`).
162-
163107
## Usage with express-graphql
164108

165109
To use the query complexity analysis validation rule with express-graphql, use something like the

src/estimators/directive/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Directive Estimator
2+
3+
The `directiveEstimator` lets you define the complexity of each field via GraphQL directives.
4+
That way you can define your schema and the complexity via GraphQL SDL and you don't have to
5+
change the field config manually.
6+
7+
## Usage
8+
9+
Add estimator to rule:
10+
11+
```typescript
12+
import queryComplexity, {directiveEstimator} from 'graphql-query-complexity';
13+
14+
const rule = queryComplexity({
15+
estimators: [
16+
directiveEstimator({
17+
// Optionally change the name of the directive here... Default value is `complexity`
18+
name: 'complexity'
19+
})
20+
]
21+
// ... other config
22+
});
23+
```
24+
25+
Define your schema and add the complexity directive:
26+
27+
```graphql
28+
type Query {
29+
# Set the complexity values on the fields via the directive
30+
someField: String @complexity(value: 5)
31+
}
32+
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Created by Ivo Meißner on 28.07.17.
3+
*/
4+
5+
import {
6+
parse,
7+
TypeInfo,
8+
ValidationContext,
9+
visit,
10+
visitWithTypeInfo,
11+
} from 'graphql';
12+
13+
import {expect} from 'chai';
14+
15+
import schema from './fixtures/schema';
16+
17+
import ComplexityVisitor from '../../../QueryComplexity';
18+
import directiveEstimator from '../index';
19+
20+
describe('directiveEstimator analysis', () => {
21+
const typeInfo = new TypeInfo(schema);
22+
23+
it('should read complexity from directive', () => {
24+
const ast = parse(`
25+
query {
26+
scalar
27+
}
28+
`);
29+
30+
const context = new ValidationContext(schema, ast, typeInfo);
31+
const visitor = new ComplexityVisitor(context, {
32+
maximumComplexity: 100,
33+
estimators: [
34+
directiveEstimator()
35+
]
36+
});
37+
38+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
39+
expect(visitor.complexity).to.equal(5);
40+
});
41+
42+
it('should not allow negative cost', () => {
43+
const ast = parse(`
44+
query {
45+
negativeCostScalar
46+
}
47+
`);
48+
49+
const context = new ValidationContext(schema, ast, typeInfo);
50+
const visitor = new ComplexityVisitor(context, {
51+
maximumComplexity: 100,
52+
estimators: [
53+
directiveEstimator()
54+
]
55+
});
56+
57+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
58+
expect(visitor.complexity).to.equal(0);
59+
});
60+
61+
it('uses default directive name', () => {
62+
const ast = parse(`
63+
query {
64+
multiDirective
65+
}
66+
`);
67+
68+
const context = new ValidationContext(schema, ast, typeInfo);
69+
const visitor = new ComplexityVisitor(context, {
70+
maximumComplexity: 100,
71+
estimators: [
72+
directiveEstimator()
73+
]
74+
});
75+
76+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
77+
expect(visitor.complexity).to.equal(2);
78+
});
79+
80+
it('uses configured directive name', () => {
81+
const ast = parse(`
82+
query {
83+
multiDirective
84+
}
85+
`);
86+
87+
const context = new ValidationContext(schema, ast, typeInfo);
88+
const visitor = new ComplexityVisitor(context, {
89+
maximumComplexity: 100,
90+
estimators: [
91+
directiveEstimator({
92+
name: 'cost'
93+
})
94+
]
95+
});
96+
97+
visit(ast, visitWithTypeInfo(typeInfo, visitor));
98+
expect(visitor.complexity).to.equal(1);
99+
});
100+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Created by Ivo Meißner on 28.07.17.
3+
*/
4+
5+
import {
6+
buildSchema
7+
} from 'graphql';
8+
9+
export default buildSchema(`
10+
type Query {
11+
scalar: String @complexity(value: 5)
12+
negativeCostScalar: String @complexity(value: -20)
13+
multiDirective: String @cost(value: 1) @complexity(value: 2)
14+
}
15+
`);

src/estimators/directive/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {ComplexityEstimator, ComplexityEstimatorArgs} from '../../QueryComplexity';
2+
import {getDirectiveValues, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString} from 'graphql';
3+
import { GraphQLDirective } from 'graphql/type/directives';
4+
import { DirectiveLocation } from 'graphql/language/directiveLocation';
5+
6+
export default function (options?: {}): ComplexityEstimator {
7+
const mergedOptions = {
8+
name: 'complexity',
9+
...(options || {})
10+
};
11+
12+
const directive = new GraphQLDirective({
13+
name: mergedOptions.name,
14+
description: 'Define a relation between the field and other nodes',
15+
locations: [
16+
DirectiveLocation.FIELD,
17+
],
18+
args: {
19+
value: {
20+
type: new GraphQLNonNull(GraphQLInt),
21+
description: 'The complexity value for the field'
22+
},
23+
multipliers: {
24+
type: new GraphQLList(new GraphQLNonNull(GraphQLString))
25+
}
26+
},
27+
});
28+
29+
return (args: ComplexityEstimatorArgs) => {
30+
const values = getDirectiveValues(directive, args.field.astNode);
31+
return values.value + args.childComplexity;
32+
};
33+
}

src/estimators/fieldConfig/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ the estimator does not return any value and the next estimator in the chain is e
66

77
## Usage
88

9-
````typescript
9+
```typescript
1010
import queryComplexity, {fieldConfigEstimator} from 'graphql-query-complexity';
1111

1212
const rule = queryComplexity({
@@ -15,7 +15,7 @@ const rule = queryComplexity({
1515
]
1616
// ... other config
1717
});
18-
````
18+
```
1919

2020
You can set a custom complexity as a numeric value in the field config:
2121

src/estimators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export {default as simpleEstimator} from './simple';
22
export {default as legacyEstimator} from './legacy';
33
export {default as fieldConfigEstimator} from './fieldConfig';
4+
export {default as directiveEstimator} from './directive';

0 commit comments

Comments
 (0)