Skip to content

Commit 46c5309

Browse files
authored
feat(handler): Add validationRules option for extending or replacing the GraphQL validation rule set (#51)
1 parent d54b4fe commit 46c5309

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

docs/interfaces/handler.HandlerOptions.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- [rootValue](handler.HandlerOptions.md#rootvalue)
2626
- [schema](handler.HandlerOptions.md#schema)
2727
- [validate](handler.HandlerOptions.md#validate)
28+
- [validationRules](handler.HandlerOptions.md#validationrules)
2829

2930
## Properties
3031

@@ -249,3 +250,20 @@ Will not be used when implementing a custom `onSubscribe`.
249250
##### Returns
250251

251252
`ReadonlyArray`<`GraphQLError`\>
253+
254+
___
255+
256+
### validationRules
257+
258+
`Optional` **validationRules**: readonly `ValidationRule`[] \| (`req`: [`Request`](handler.Request.md)<`RequestRaw`, `RequestContext`\>, `args`: [`OperationArgs`](../modules/handler.md#operationargs)<`Context`\>, `specifiedRules`: readonly `ValidationRule`[]) => readonly `ValidationRule`[] \| `Promise`<readonly `ValidationRule`[]\>
259+
260+
The validation rules for running GraphQL validate.
261+
262+
When providing an array, the rules will be APPENDED to the default
263+
`specifiedRules` array provided by the graphql-js module.
264+
265+
Alternatively, providing a function instead will OVERWRITE the defaults
266+
and use exclusively the rules returned by the function. The third (last)
267+
argument of the function are the default `specifiedRules` array provided
268+
by the graphql-js module, you're free to prepend/append the defaults to
269+
your rule set, or omit them altogether.

src/__tests__/handler.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,58 @@ it('should correctly serialise execution result errors', async () => {
120120
}
121121
`);
122122
});
123+
124+
it('should append the provided validation rules array', async () => {
125+
const server = startTServer({
126+
validationRules: [
127+
(ctx) => {
128+
ctx.reportError(new GraphQLError('Woah!'));
129+
return {};
130+
},
131+
],
132+
});
133+
const url = new URL(server.url);
134+
url.searchParams.set('query', '{ idontexist }');
135+
const result = await fetch(url.toString());
136+
await expect(result.json()).resolves.toMatchInlineSnapshot(`
137+
{
138+
"errors": [
139+
{
140+
"message": "Woah!",
141+
},
142+
{
143+
"locations": [
144+
{
145+
"column": 3,
146+
"line": 1,
147+
},
148+
],
149+
"message": "Cannot query field "idontexist" on type "Query".",
150+
},
151+
],
152+
}
153+
`);
154+
});
155+
156+
it('should replace the validation rules when providing a function', async () => {
157+
const server = startTServer({
158+
validationRules: () => [
159+
(ctx) => {
160+
ctx.reportError(new GraphQLError('Woah!'));
161+
return {};
162+
},
163+
],
164+
});
165+
const url = new URL(server.url);
166+
url.searchParams.set('query', '{ idontexist }');
167+
const result = await fetch(url.toString());
168+
await expect(result.json()).resolves.toMatchInlineSnapshot(`
169+
{
170+
"errors": [
171+
{
172+
"message": "Woah!",
173+
},
174+
],
175+
}
176+
`);
177+
});

src/handler.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
ExecutionResult,
1010
GraphQLSchema,
1111
validate as graphqlValidate,
12+
ValidationRule,
13+
specifiedRules,
1214
execute as graphqlExecute,
1315
parse as graphqlParse,
1416
DocumentNode,
@@ -203,6 +205,25 @@ export interface HandlerOptions<
203205
* Will not be used when implementing a custom `onSubscribe`.
204206
*/
205207
validate?: typeof graphqlValidate;
208+
/**
209+
* The validation rules for running GraphQL validate.
210+
*
211+
* When providing an array, the rules will be APPENDED to the default
212+
* `specifiedRules` array provided by the graphql-js module.
213+
*
214+
* Alternatively, providing a function instead will OVERWRITE the defaults
215+
* and use exclusively the rules returned by the function. The third (last)
216+
* argument of the function are the default `specifiedRules` array provided
217+
* by the graphql-js module, you're free to prepend/append the defaults to
218+
* your rule set, or omit them altogether.
219+
*/
220+
validationRules?:
221+
| readonly ValidationRule[]
222+
| ((
223+
req: Request<RequestRaw, RequestContext>,
224+
args: OperationArgs<Context>,
225+
specifiedRules: readonly ValidationRule[],
226+
) => Promise<readonly ValidationRule[]> | readonly ValidationRule[]);
206227
/**
207228
* Is the `execute` function from GraphQL which is
208229
* used to execute the query and mutation operations.
@@ -373,6 +394,7 @@ export function createHandler<
373394
schema,
374395
context,
375396
validate = graphqlValidate,
397+
validationRules = [],
376398
execute = graphqlExecute,
377399
parse = graphqlParse,
378400
getOperationAST = graphqlGetOperationAST,
@@ -545,7 +567,13 @@ export function createHandler<
545567
};
546568
}
547569

548-
const validationErrs = validate(args.schema, args.document);
570+
let rules = specifiedRules;
571+
if (typeof validationRules === 'function') {
572+
rules = await validationRules(req, args, specifiedRules);
573+
} else {
574+
rules = [...rules, ...validationRules];
575+
}
576+
const validationErrs = validate(args.schema, args.document, rules);
549577
if (validationErrs.length) {
550578
return makeResponse(validationErrs, acceptedMediaType);
551579
}

0 commit comments

Comments
 (0)