Skip to content

Commit 206a859

Browse files
committed
Merge branch 'dev' of into sh/middleware-framework
2 parents 1e67be0 + 35a928d commit 206a859

File tree

5 files changed

+580
-22
lines changed

5 files changed

+580
-22
lines changed

src/@types/buildTypeWeights.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
interface Fields {
2-
[index: string]: number;
2+
readonly [index: string]: number | ((arg: number, type: Type) => number);
33
}
44

55
interface Type {
6-
weight: number;
7-
fields: Fields;
6+
readonly weight: number;
7+
readonly fields: Fields;
88
}
99

1010
interface TypeWeightObject {
11-
[index: string]: Type;
11+
readonly [index: string]: Type;
1212
}
1313

1414
interface TypeWeightConfig {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { parse } from 'graphql';
2+
3+
/**
4+
* This function should
5+
* 1. validate the query using graphql methods
6+
* 2. parse the query string using the graphql parse method
7+
* 3. itreate through the query AST and
8+
* - cross reference the type weight object to check type weight
9+
* - total all the eweights of all types in the query
10+
* 4. return the total as the query complexity
11+
*
12+
* TO DO: extend the functionality to work for mutations and subscriptions
13+
*
14+
* @param {string} queryString
15+
* @param {TypeWeightObject} typeWeights
16+
* @param {string} complexityOption
17+
*/
18+
function getQueryTypeComplexity(queryString: string, typeWeights: TypeWeightObject): number {
19+
throw Error('getQueryComplexity is not implemented.');
20+
}
21+
22+
export default getQueryTypeComplexity;

src/rateLimiters/tokenBucket.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { RedisClientType } from 'redis';
99
* 4. Otherwise, disallow the request and do not update the token total.
1010
*/
1111
class TokenBucket implements RateLimiter {
12-
capacity: number;
12+
private capacity: number;
1313

14-
refillRate: number;
14+
private refillRate: number;
1515

16-
client: RedisClientType;
16+
private client: RedisClientType;
1717

1818
/**
1919
* Create a new instance of a TokenBucket rate limiter that can be connected to any database store
@@ -29,6 +29,15 @@ class TokenBucket implements RateLimiter {
2929
throw Error('TokenBucket refillRate and capacity must be positive');
3030
}
3131

32+
/**
33+
*
34+
*
35+
* @param {string} uuid - unique identifer used to throttle requests
36+
* @param {number} timestamp - time the request was recieved
37+
* @param {number} [tokens=1] - complexity of the query for throttling requests
38+
* @return {*} {Promise<RateLimiterResponse>}
39+
* @memberof TokenBucket
40+
*/
3241
async processRequest(
3342
uuid: string,
3443
timestamp: number,

test/analysis/buildTypeWeights.test.ts

Lines changed: 226 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@ import { buildSchema } from 'graphql';
22
import { GraphQLSchema } from 'graphql/type/schema';
33
import buildTypeWeightsFromSchema from '../../src/analysis/buildTypeWeights';
44

5+
// these types allow the tests to overwite properties on the typeWeightObject
6+
interface TestFields {
7+
[index: string]: number;
8+
}
9+
10+
interface TestType {
11+
weight: number;
12+
fields: TestFields;
13+
}
14+
15+
interface TestTypeWeightObject {
16+
[index: string]: TestType;
17+
}
18+
519
xdescribe('Test buildTypeWeightsFromSchema function', () => {
620
let schema: GraphQLSchema;
721

@@ -28,18 +42,25 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
2842

2943
test('multiple types', () => {
3044
schema = buildSchema(`
45+
type Query {
46+
user: User,
47+
movie: Movie,
48+
}
3149
type User {
3250
name: String
3351
email: String
3452
}
35-
3653
type Movie {
3754
name: String
3855
director: String
3956
}
4057
`);
4158

4259
expect(buildTypeWeightsFromSchema(schema)).toEqual({
60+
Query: {
61+
weight: 1,
62+
fields: {},
63+
},
4364
User: {
4465
weight: 1,
4566
fields: {
@@ -63,12 +84,10 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
6384
user: User
6485
movie: Movie
6586
}
66-
6787
type User {
6888
name: String
69-
email: String
89+
film: Movie
7090
}
71-
7291
type Movie {
7392
name: String
7493
director: User
@@ -121,29 +140,220 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
121140
});
122141
});
123142

143+
test('types with arguments', () => {
144+
schema = buildSchema(`
145+
type Query {
146+
character(id: ID!): Character
147+
}
148+
type Character {
149+
id: ID!
150+
name: String!
151+
}`);
152+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
153+
Query: {
154+
weight: 1,
155+
fields: {},
156+
},
157+
Character: {
158+
weight: 1,
159+
fields: {
160+
id: 0,
161+
name: 0,
162+
},
163+
},
164+
});
165+
});
166+
167+
test('enum types', () => {
168+
schema = buildSchema(`
169+
type Query {
170+
hero(episode: Episode): Character
171+
}
172+
type Character {
173+
id: ID!
174+
name: String!
175+
}
176+
enum Episode {
177+
NEWHOPE
178+
EMPIRE
179+
JEDI
180+
}`);
181+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
182+
Query: {
183+
weight: 1,
184+
fields: {},
185+
},
186+
Character: {
187+
weight: 1,
188+
fields: {
189+
id: 0,
190+
name: 0,
191+
},
192+
},
193+
Episode: {
194+
weight: 0,
195+
fields: {},
196+
},
197+
});
198+
});
199+
200+
test('fields returning lists of objects of determinate size', () => {
201+
schema = buildSchema(`
202+
type Query {
203+
reviews(episode: Episode!, first: Int): [Review]
204+
}
205+
type Review {
206+
episode: Episode
207+
stars: Int!
208+
commentary: String
209+
}
210+
enum Episode {
211+
NEWHOPE
212+
EMPIRE
213+
JEDI
214+
}`);
215+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
216+
Query: {
217+
weight: 1,
218+
fields: {
219+
// FIXME: check the best solution during implementation and update the tests here.
220+
reviews: (arg: number, type: Type) => arg * type.weight,
221+
// code from PR review -> reviews: (type) => args[multiplierName] * typeWeightObject[type].weight
222+
},
223+
},
224+
Review: {
225+
weight: 1,
226+
fields: {
227+
stars: 0,
228+
commentary: 0,
229+
},
230+
},
231+
Episode: {
232+
weight: 0,
233+
fields: {},
234+
},
235+
});
236+
});
237+
238+
// TODO: need to figure out how to handle this situation. Skip for now.
239+
// The field friends returns a list of an unknown number of objects.
240+
xtest('fields returning lists of objects of indetermitae size', () => {
241+
schema = buildSchema(`
242+
type Human {
243+
id: ID!
244+
name: String!
245+
homePlanet: String
246+
friends: [Human]
247+
}
248+
`);
249+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
250+
Human: {
251+
weight: 1,
252+
fields: {
253+
// FIXME: check the best solution during implementation and update the tests here.
254+
friends: (arg: number, type: Type) => arg * type.weight,
255+
},
256+
},
257+
});
258+
});
259+
260+
test('interface types', () => {
261+
schema = buildSchema(`
262+
interface Character {
263+
id: ID!
264+
name: String!
265+
}
266+
267+
type Human implements Character {
268+
id: ID!
269+
name: String!
270+
homePlanet: String
271+
}
272+
273+
type Droid implements Character {
274+
id: ID!
275+
name: String!
276+
primaryFunction: String
277+
}`);
278+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
279+
Character: {
280+
weight: 1,
281+
fields: {
282+
id: 0,
283+
name: 0,
284+
},
285+
},
286+
Human: {
287+
weight: 1,
288+
fields: {
289+
id: 0,
290+
name: 0,
291+
homePlanet: 0,
292+
},
293+
},
294+
Droid: {
295+
weight: 1,
296+
fields: {
297+
id: 0,
298+
name: 0,
299+
primaryFunction: 0,
300+
},
301+
},
302+
Episode: {
303+
weight: 0,
304+
fields: {},
305+
},
306+
});
307+
});
308+
309+
test('union tyes', () => {
310+
schema = buildSchema(`
311+
union SearchResult = Human | Droid
312+
type Human{
313+
homePlanet: String
314+
}
315+
type Droid {
316+
primaryFunction: String
317+
}`);
318+
expect(buildTypeWeightsFromSchema(schema)).toEqual({
319+
SearchResult: {
320+
weight: 1,
321+
fields: {},
322+
},
323+
human: {
324+
weight: 1,
325+
fields: {
326+
homePlanet: 0,
327+
},
328+
},
329+
droid: {
330+
weight: 1,
331+
fields: {
332+
primaryFunction: 0,
333+
},
334+
},
335+
});
336+
});
337+
124338
// TODO: Tests should be written to acount for the additional scenarios possible in a schema
125339
// Mutation type
340+
// Input types (a part of mutations?)
126341
// Subscription type
127-
// List type
128-
// Enem types
129-
// Interface
130-
// Unions
131-
// Input types
132342
});
133343

134344
describe('changes "type weight object" type weights with user configuration of...', () => {
135-
let expectedOutput: TypeWeightObject;
345+
let expectedOutput: TestTypeWeightObject;
136346

137347
beforeEach(() => {
138348
schema = buildSchema(`
139349
type Query {
140-
user: User
141-
movie: Movie
350+
user(id: ID!): User
351+
movie(id: ID!): Movie
142352
}
143353
144354
type User {
145355
name: String
146-
email: String
356+
film: Movie
147357
}
148358
149359
type Movie {
@@ -163,7 +373,6 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
163373
weight: 1,
164374
fields: {
165375
name: 0,
166-
email: 0,
167376
},
168377
},
169378
Movie: {
@@ -202,7 +411,6 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
202411
});
203412

204413
expectedOutput.user.fields.name = 2;
205-
expectedOutput.user.fields.email = 2;
206414
expectedOutput.movie.fields.name = 2;
207415

208416
expect(typeWeightObject).toEqual({ expectedOutput });
@@ -249,5 +457,8 @@ xdescribe('Test buildTypeWeightsFromSchema function', () => {
249457
'negative'
250458
);
251459
});
460+
461+
// TODO: throw validation error if schema is invalid
462+
test('schema is invalid', () => {});
252463
});
253464
});

0 commit comments

Comments
 (0)