From 46c53096cf05b4e4bfa8d140885a615d43042ae1 Mon Sep 17 00:00:00 2001 From: Denis Badurina Date: Thu, 9 Feb 2023 13:34:37 +0100 Subject: [PATCH] feat(handler): Add `validationRules` option for extending or replacing the GraphQL validation rule set (#51) --- docs/interfaces/handler.HandlerOptions.md | 18 ++++++++ src/__tests__/handler.ts | 55 +++++++++++++++++++++++ src/handler.ts | 30 ++++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/docs/interfaces/handler.HandlerOptions.md b/docs/interfaces/handler.HandlerOptions.md index 0412a13c..8ec442b7 100644 --- a/docs/interfaces/handler.HandlerOptions.md +++ b/docs/interfaces/handler.HandlerOptions.md @@ -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 @@ -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` + +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. diff --git a/src/__tests__/handler.ts b/src/__tests__/handler.ts index cecb23a7..2bc6aec9 100644 --- a/src/__tests__/handler.ts +++ b/src/__tests__/handler.ts @@ -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!", + }, + ], + } + `); +}); diff --git a/src/handler.ts b/src/handler.ts index 2cfabcf7..d8150ded 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -9,6 +9,8 @@ import { ExecutionResult, GraphQLSchema, validate as graphqlValidate, + ValidationRule, + specifiedRules, execute as graphqlExecute, parse as graphqlParse, DocumentNode, @@ -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, + args: OperationArgs, + specifiedRules: readonly ValidationRule[], + ) => Promise | readonly ValidationRule[]); /** * Is the `execute` function from GraphQL which is * used to execute the query and mutation operations. @@ -373,6 +394,7 @@ export function createHandler< schema, context, validate = graphqlValidate, + validationRules = [], execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, @@ -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); }