diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c9cb29d..85c0d9ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ ### Features - **Breaking Change**: upgrade `ArgumentValidationError` and replace `UnauthorizedError` and `ForbiddenError` with `AuthenticationError`, `AuthorizationError` that are extending `GraphQLError` to let the error details be accessible in the `extensions` property - **Breaking Change**: change `ClassType` constraint from `ClassType` to `ClassType` in order to make it work properly with new TS features -- **Breaking Change**: removed `dateScalarMode` option from `buildSchema` +- **Breaking Change**: remove `dateScalarMode` option from `buildSchema` - **Breaking Change**: make `graphql-scalars` package a peer dependency and use date scalars from it instead of custom ones - **Breaking Change**: exported `GraphQLISODateTime` scalar has now a name `DateTimeISO` +- **Breaking Change**: change `ValidatorFn` signature from `ValidatorFn` to `ValidatorFn` +- support custom validation function getting resolver data on validate ### Fixes - allow `ValidatorFn` to accept array of values (instead of only `object | undefined`) diff --git a/src/decorators/createMethodDecorator.ts b/src/decorators/createMethodDecorator.ts index a81b0583d..d1778b193 100644 --- a/src/decorators/createMethodDecorator.ts +++ b/src/decorators/createMethodDecorator.ts @@ -1,7 +1,7 @@ import { UseMiddleware } from "./UseMiddleware"; import { MiddlewareFn } from "../interfaces/Middleware"; -export function createMethodDecorator( +export function createMethodDecorator( resolver: MiddlewareFn, ): MethodDecorator { return UseMiddleware(resolver); diff --git a/src/decorators/createParamDecorator.ts b/src/decorators/createParamDecorator.ts index 2e628f335..56a428604 100644 --- a/src/decorators/createParamDecorator.ts +++ b/src/decorators/createParamDecorator.ts @@ -3,7 +3,7 @@ import { getMetadataStorage } from "../metadata/getMetadataStorage"; import { SymbolKeysNotSupportedError } from "../errors"; import { ParameterDecorator } from "../interfaces/LegacyDecorators"; -export function createParamDecorator( +export function createParamDecorator( resolver: (resolverData: ResolverData) => any, ): ParameterDecorator { return (prototype, propertyKey, parameterIndex) => { diff --git a/src/interfaces/AuthChecker.ts b/src/interfaces/AuthChecker.ts index 4ed40bb7f..cb37a5920 100644 --- a/src/interfaces/AuthChecker.ts +++ b/src/interfaces/AuthChecker.ts @@ -1,16 +1,16 @@ import { ClassType } from "./ClassType"; import { ResolverData } from "./ResolverData"; -export type AuthCheckerFn = ( +export type AuthCheckerFn = ( resolverData: ResolverData, roles: TRoleType[], ) => boolean | Promise; -export type AuthCheckerInterface = { +export type AuthCheckerInterface = { check(resolverData: ResolverData, roles: TRoleType[]): boolean | Promise; }; -export type AuthChecker = +export type AuthChecker = | AuthCheckerFn | ClassType>; diff --git a/src/interfaces/Middleware.ts b/src/interfaces/Middleware.ts index b71c67e4f..bc7387d58 100644 --- a/src/interfaces/Middleware.ts +++ b/src/interfaces/Middleware.ts @@ -2,16 +2,18 @@ import { ResolverData } from "./ResolverData"; export type NextFn = () => Promise; -export type MiddlewareFn = ( +export type MiddlewareFn = ( action: ResolverData, next: NextFn, ) => Promise; -export interface MiddlewareInterface { +export interface MiddlewareInterface { use: MiddlewareFn; } -export interface MiddlewareClass { +export interface MiddlewareClass { new (...args: any[]): MiddlewareInterface; } -export type Middleware = MiddlewareFn | MiddlewareClass; +export type Middleware = + | MiddlewareFn + | MiddlewareClass; diff --git a/src/interfaces/ResolverData.ts b/src/interfaces/ResolverData.ts index de129699f..e75aa7720 100644 --- a/src/interfaces/ResolverData.ts +++ b/src/interfaces/ResolverData.ts @@ -4,9 +4,9 @@ export interface ArgsDictionary { [argName: string]: any; } -export interface ResolverData { +export interface ResolverData { root: any; args: ArgsDictionary; - context: ContextType; + context: TContextType; info: GraphQLResolveInfo; } diff --git a/src/interfaces/ValidatorFn.ts b/src/interfaces/ValidatorFn.ts index 0d55eac7b..e6b5b88a4 100644 --- a/src/interfaces/ValidatorFn.ts +++ b/src/interfaces/ValidatorFn.ts @@ -1,6 +1,7 @@ import { TypeValue } from "../decorators/types"; +import { ResolverData } from "./ResolverData"; -export type ValidatorFn = ( +export type ValidatorFn = ( /** * The value of the argument. * It can by of any type, which means: @@ -12,4 +13,5 @@ export type ValidatorFn = ( */ argValue: any | undefined, argType: TypeValue, + resolverData: ResolverData, ) => void | Promise; diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index 06b0606da..146cfefde 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -27,6 +27,7 @@ export function getParams( return validateArg( convertArgsToInstance(paramInfo, resolverData.args), paramInfo.getType(), + resolverData, globalValidate, paramInfo.validate, validateFn, @@ -35,6 +36,7 @@ export function getParams( return validateArg( convertArgToInstance(paramInfo, resolverData.args), paramInfo.getType(), + resolverData, globalValidate, paramInfo.validate, validateFn, diff --git a/src/resolvers/validate-arg.ts b/src/resolvers/validate-arg.ts index 6a503781e..2aef4e32d 100644 --- a/src/resolvers/validate-arg.ts +++ b/src/resolvers/validate-arg.ts @@ -5,6 +5,7 @@ import { TypeValue } from "../decorators/types"; import { ArgumentValidationError } from "../errors"; import { ValidateSettings } from "../schema/build-context"; import { ValidatorFn } from "../interfaces/ValidatorFn"; +import { ResolverData } from "../interfaces"; const shouldArgBeValidated = (argValue: unknown): boolean => argValue !== null && typeof argValue === "object"; @@ -12,12 +13,13 @@ const shouldArgBeValidated = (argValue: unknown): boolean => export async function validateArg( argValue: any | undefined, argType: TypeValue, + resolverData: ResolverData, globalValidate: ValidateSettings, argValidate: ValidateSettings | undefined, validateFn: ValidatorFn | undefined, ): Promise { if (typeof validateFn === "function") { - await validateFn(argValue, argType); + await validateFn(argValue, argType, resolverData); return argValue; } diff --git a/tests/functional/validation.ts b/tests/functional/validation.ts index 517a62361..4eb89a32f 100644 --- a/tests/functional/validation.ts +++ b/tests/functional/validation.ts @@ -15,6 +15,7 @@ import { ArgumentValidationError, Args, ArgsType, + ResolverData, } from "../../src"; import { TypeValue } from "../../src/decorators/types"; @@ -678,6 +679,7 @@ describe("Custom validation", () => { let validateArgs: Array = []; let validateTypes: TypeValue[] = []; + let validateResolverData: ResolverData[] = []; let sampleQueryArgs: any[] = []; beforeAll(async () => { @@ -721,17 +723,23 @@ describe("Custom validation", () => { it("should call `validateFn` function provided in option with proper params", async () => { schema = await buildSchema({ resolvers: [sampleResolverCls], - validateFn: (arg, type) => { + validateFn: (arg, type, resolverData) => { validateArgs.push(arg); validateTypes.push(type); + validateResolverData.push(resolverData); }, }); - await graphql({ schema, source: document }); + await graphql({ schema, source: document, contextValue: { isContext: true } }); expect(validateArgs).toEqual([{ sampleField: "sampleFieldValue" }]); expect(validateArgs[0]).toBeInstanceOf(sampleArgsCls); expect(validateTypes).toEqual([sampleArgsCls]); + expect(validateResolverData).toEqual([ + expect.objectContaining({ + context: { isContext: true }, + }), + ]); }); it("should let `validateFn` function handle array args", async () => {