Skip to content

feat(handler): Add validationRules option for extending or replacing the GraphQL validation rule set #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/interfaces/handler.HandlerOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [rootValue](handler.HandlerOptions.md#rootvalue)
- [schema](handler.HandlerOptions.md#schema)
- [validate](handler.HandlerOptions.md#validate)
- [validationRules](handler.HandlerOptions.md#validationrules)

## Properties

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

`ReadonlyArray`<`GraphQLError`\>

___

### validationRules

• `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`[]\>

The validation rules for running GraphQL validate.

When providing an array, the rules will be APPENDED to the default
`specifiedRules` array provided by the graphql-js module.

Alternatively, providing a function instead will OVERWRITE the defaults
and use exclusively the rules returned by the function. The third (last)
argument of the function are the default `specifiedRules` array provided
by the graphql-js module, you're free to prepend/append the defaults to
your rule set, or omit them altogether.
55 changes: 55 additions & 0 deletions src/__tests__/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,58 @@ it('should correctly serialise execution result errors', async () => {
}
`);
});

it('should append the provided validation rules array', async () => {
const server = startTServer({
validationRules: [
(ctx) => {
ctx.reportError(new GraphQLError('Woah!'));
return {};
},
],
});
const url = new URL(server.url);
url.searchParams.set('query', '{ idontexist }');
const result = await fetch(url.toString());
await expect(result.json()).resolves.toMatchInlineSnapshot(`
{
"errors": [
{
"message": "Woah!",
},
{
"locations": [
{
"column": 3,
"line": 1,
},
],
"message": "Cannot query field "idontexist" on type "Query".",
},
],
}
`);
});

it('should replace the validation rules when providing a function', async () => {
const server = startTServer({
validationRules: () => [
(ctx) => {
ctx.reportError(new GraphQLError('Woah!'));
return {};
},
],
});
const url = new URL(server.url);
url.searchParams.set('query', '{ idontexist }');
const result = await fetch(url.toString());
await expect(result.json()).resolves.toMatchInlineSnapshot(`
{
"errors": [
{
"message": "Woah!",
},
],
}
`);
});
30 changes: 29 additions & 1 deletion src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ExecutionResult,
GraphQLSchema,
validate as graphqlValidate,
ValidationRule,
specifiedRules,
execute as graphqlExecute,
parse as graphqlParse,
DocumentNode,
Expand Down Expand Up @@ -203,6 +205,25 @@ export interface HandlerOptions<
* Will not be used when implementing a custom `onSubscribe`.
*/
validate?: typeof graphqlValidate;
/**
* The validation rules for running GraphQL validate.
*
* When providing an array, the rules will be APPENDED to the default
* `specifiedRules` array provided by the graphql-js module.
*
* Alternatively, providing a function instead will OVERWRITE the defaults
* and use exclusively the rules returned by the function. The third (last)
* argument of the function are the default `specifiedRules` array provided
* by the graphql-js module, you're free to prepend/append the defaults to
* your rule set, or omit them altogether.
*/
validationRules?:
| readonly ValidationRule[]
| ((
req: Request<RequestRaw, RequestContext>,
args: OperationArgs<Context>,
specifiedRules: readonly ValidationRule[],
) => Promise<readonly ValidationRule[]> | readonly ValidationRule[]);
/**
* Is the `execute` function from GraphQL which is
* used to execute the query and mutation operations.
Expand Down Expand Up @@ -373,6 +394,7 @@ export function createHandler<
schema,
context,
validate = graphqlValidate,
validationRules = [],
execute = graphqlExecute,
parse = graphqlParse,
getOperationAST = graphqlGetOperationAST,
Expand Down Expand Up @@ -545,7 +567,13 @@ export function createHandler<
};
}

const validationErrs = validate(args.schema, args.document);
let rules = specifiedRules;
if (typeof validationRules === 'function') {
rules = await validationRules(req, args, specifiedRules);
} else {
rules = [...rules, ...validationRules];
}
const validationErrs = validate(args.schema, args.document, rules);
if (validationErrs.length) {
return makeResponse(validationErrs, acceptedMediaType);
}
Expand Down