From 3358fa71c4138f906d22e5a41d4bd23ce040a922 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Thu, 9 Feb 2023 12:59:14 +0100 Subject: [PATCH 1/3] validation rules option --- docs/interfaces/handler.HandlerOptions.md | 18 ++++++++++++++ src/handler.ts | 29 ++++++++++++++++++++++- 2 files changed, 46 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/handler.ts b/src/handler.ts index 2cfabcf7..3f23328c 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,12 @@ export function createHandler< }; } - const validationErrs = validate(args.schema, args.document); + const validationErrs = validate(args.schema, args.document, [ + ...specifiedRules, + ...(typeof validationRules === 'function' + ? await validationRules(req, args, specifiedRules) + : validationRules), + ]); if (validationErrs.length) { return makeResponse(validationErrs, acceptedMediaType); } From 6b0aa3a63e95d366d782e6c58630f503ef2466ff Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Thu, 9 Feb 2023 13:10:02 +0100 Subject: [PATCH 2/3] microfixes --- src/handler.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/handler.ts b/src/handler.ts index 3f23328c..d8150ded 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -567,12 +567,13 @@ export function createHandler< }; } - const validationErrs = validate(args.schema, args.document, [ - ...specifiedRules, - ...(typeof validationRules === 'function' - ? await validationRules(req, args, specifiedRules) - : validationRules), - ]); + 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); } From 3d58e8fa39e62fd1bfd1fbf14c6095bdcad8efd4 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Thu, 9 Feb 2023 13:10:06 +0100 Subject: [PATCH 3/3] tests --- src/__tests__/handler.ts | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) 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!", + }, + ], + } + `); +});