Skip to content

Commit b6b31cf

Browse files
committed
Merge branch 'em/typeWeightsTest' of https://github.com/oslabs-beta/GraphQL-Gate into em/typeWeightsTest
2 parents 211fc29 + e76c0ef commit b6b31cf

File tree

5 files changed

+222
-10
lines changed

5 files changed

+222
-10
lines changed

package-lock.json

Lines changed: 141 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@babel/core": "^7.17.12",
2626
"@babel/preset-env": "^7.17.12",
2727
"@babel/preset-typescript": "^7.17.12",
28+
"@types/express": "^4.17.13",
2829
"@types/jest": "^27.5.1",
2930
"@types/redis-mock": "^0.17.1",
3031
"@typescript-eslint/eslint-plugin": "^5.24.0",
@@ -49,7 +50,7 @@
4950
"*.{js,ts,css,md}": "prettier --write --ignore-unknown"
5051
},
5152
"dependencies": {
52-
"redis": "^4.1.0",
53-
"graphql": "^16.5.0"
53+
"graphql": "^16.5.0",
54+
"redis": "^4.1.0"
5455
}
5556
}

src/@types/rateLimit.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,22 @@ interface RedisBucket {
2222
tokens: number;
2323
timestamp: number;
2424
}
25+
26+
type RateLimiterSelection =
27+
| 'TOKEN_BUCKET'
28+
| 'LEAKY_BUCKET'
29+
| 'FIXED_WINDOW'
30+
| 'SLIDING_WINDOW_LOG'
31+
| 'SLIDING_WINDOW_COUNTER';
32+
33+
/**
34+
* @type {number} bucketSize - Size of the token bucket
35+
* @type {number} refillRate - Rate at which tokens are added to the bucket in seconds
36+
*/
37+
interface TokenBucketOptions {
38+
bucketSize: number;
39+
refillRate: number;
40+
}
41+
42+
// TODO: This will be a union type where we can specify Option types for other Rate Limiters
43+
type RateLimiterOptions = TokenBucketOptions;

src/analysis/buildTypeWeights.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
import { GraphQLSchema } from 'graphql/type/schema';
22

3+
/**
4+
* Default TypeWeight Configuration:
5+
* mutation: 10
6+
* object: 1
7+
* scalar: 0
8+
* connection: 2
9+
*/
10+
export const defaultTypeWeightsConfig: TypeWeightConfig = {
11+
mutation: 10,
12+
object: 1,
13+
scalar: 0,
14+
connection: 2,
15+
};
16+
317
/**
418
* The default typeWeightsConfig object is based off of Shopifys implementation of query
519
* cost analysis. Our function should input a users configuration of type weights or fall
@@ -10,17 +24,11 @@ import { GraphQLSchema } from 'graphql/type/schema';
1024
* - validate that the typeWeightsConfig parameter has no negative values (throw an error if it does)
1125
*
1226
* @param schema
13-
* @param typeWeightsConfig
27+
* @param typeWeightsConfig Defaults to {mutation: 10, object: 1, field: 0, connection: 2}
1428
*/
1529
function buildTypeWeightsFromSchema(
1630
schema: GraphQLSchema,
17-
typeWeightsConfig: TypeWeightConfig = {
18-
mutation: 10, // mutation
19-
object: 1, // itnterfaces, unions, objects, query
20-
scalar: 0, // enums, scalars
21-
connection: 2, // pagination stuff
22-
// ? subscription
23-
}
31+
typeWeightsConfig: TypeWeightConfig = defaultTypeWeightsConfig
2432
): TypeWeightObject {
2533
throw Error(`getTypeWeightsFromSchema is not implemented.`);
2634
}

src/middleware/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { RedisClientOptions } from 'redis';
2+
import { Request, Response, NextFunction, RequestHandler } from 'express';
3+
import { GraphQLSchema } from 'graphql/type/schema';
4+
import { defaultTypeWeightsConfig } from '../analysis/buildTypeWeights';
5+
6+
// FIXME: Will the developer be responsible for first parsing the schema from a file?
7+
// Can consider accepting a string representing a the filepath to a schema
8+
// FIXME: Should a 429 status be sent by default or do we allow the user to handle blocked requests?
9+
10+
/**
11+
* Primary entry point for adding GraphQL Rate Limiting middleware to an Express Server
12+
* @param {RateLimiterSelection} rateLimiter Specify rate limiting algorithm to be used
13+
* @param {RateLimiterOptions} options Specify the appropriate options for the selected rateLimiter
14+
* @param {GraphQLSchema} schema GraphQLSchema object
15+
* @param {RedisClientOptions} redisClientOptions valid node-redis connection options. See https://github.com/redis/node-redis/blob/HEAD/docs/client-configuration.md
16+
* @param {TypeWeightConfig} typeWeightConfig Optional type weight configuration for the GraphQL Schema.
17+
* Defaults to {mutation: 10, object: 1, field: 0, connection: 2}
18+
* @returns {RequestHandler} express middleware that computes the complexity of req.query and calls the next middleware
19+
* if the query is allowed or sends a 429 status if the request is blocked
20+
* @throws ValidationError if GraphQL Schema is invalid
21+
*/
22+
export function expressRateLimiter(
23+
rateLimiter: RateLimiterSelection,
24+
rateLimiterOptions: RateLimiterOptions,
25+
schema: GraphQLSchema,
26+
redisClientOptions: RedisClientOptions,
27+
typeWeightConfig: TypeWeightConfig = defaultTypeWeightsConfig
28+
): RequestHandler {
29+
// TODO: Set 'timestamp' on res.locals to record when the request is received in UNIX format. HTTP does not inlude this.
30+
// TODO: Parse the schema to create a TypeWeightObject. Throw ValidationError if schema is invalid
31+
// TODO: Connect to Redis store using provided options. Default to localhost:6379
32+
// TODO: Configure the selected RateLimtier
33+
// TODO: Configure the complexity analysis algorithm to run for incoming requests
34+
35+
const middleware: RequestHandler = (req: Request, res: Response, next: NextFunction) => {
36+
// TODO: Parse query from req.query, compute complexity and pass necessary info to rate limiter
37+
// TODO: Call next if query is successful, send 429 status if query blocked, call next(err) with any thrown errors
38+
next(Error('Express rate limiting middleware not implemented'));
39+
};
40+
return middleware;
41+
}
42+
43+
export default expressRateLimiter;

0 commit comments

Comments
 (0)