From f07306147e45746a68620287e9c6098c8ed565ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Magrin?= Date: Mon, 4 Dec 2023 16:48:19 +0100 Subject: [PATCH 1/2] feat: access the node instance in custom validation --- src/validation/ValidationExecutor.ts | 10 ++++++++-- src/validation/ValidatorConstraintInterface.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/validation/ValidationExecutor.ts b/src/validation/ValidationExecutor.ts index 9d3d312f14..838e79f711 100644 --- a/src/validation/ValidationExecutor.ts +++ b/src/validation/ValidationExecutor.ts @@ -24,6 +24,7 @@ export class ValidationExecutor { // Private Properties // ------------------------------------------------------------------------- + private instance: any = undefined; private metadataStorage = getMetadataStorage(); // ------------------------------------------------------------------------- @@ -51,6 +52,11 @@ export class ValidationExecutor { ); } + // Keep the instance to the original object + if (this.instance === undefined){ + this.instance = object; + } + const groups = this.validatorOptions ? this.validatorOptions.groups : undefined; const strictGroups = (this.validatorOptions && this.validatorOptions.strictGroups) || false; const always = (this.validatorOptions && this.validatorOptions.always) || false; @@ -268,7 +274,7 @@ export class ValidationExecutor { }; if (!metadata.each || !(Array.isArray(value) || value instanceof Set || value instanceof Map)) { - const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments); + const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments, this.instance); if (isPromise(validatedValue)) { const promise = validatedValue.then(isValid => { if (!isValid) { @@ -297,7 +303,7 @@ export class ValidationExecutor { const arrayValue = convertToArray(value); // Validation needs to be applied to each array item const validatedSubValues = arrayValue.map((subValue: any) => - customConstraintMetadata.instance.validate(subValue, validationArguments) + customConstraintMetadata.instance.validate(subValue, validationArguments, this.instance) ); const validationIsAsync = validatedSubValues.some((validatedSubValue: boolean | Promise) => isPromise(validatedSubValue) diff --git a/src/validation/ValidatorConstraintInterface.ts b/src/validation/ValidatorConstraintInterface.ts index f29ce2f6fe..591b2e5d59 100644 --- a/src/validation/ValidatorConstraintInterface.ts +++ b/src/validation/ValidatorConstraintInterface.ts @@ -6,7 +6,7 @@ export interface ValidatorConstraintInterface { /** * Method to be called to perform custom validation over given value. */ - validate(value: any, validationArguments?: ValidationArguments): Promise | boolean; + validate(value: any, validationArguments?: ValidationArguments, instance?: any): Promise | boolean; /** * Gets default message when validation for this constraint fail. From cb8f1d77dac97de22d4ada213255db4ee080e9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Magrin?= Date: Mon, 4 Dec 2023 17:02:04 +0100 Subject: [PATCH 2/2] feat: minor changes move `instance` into ValidationArguments --- README.md | 2 ++ src/validation/ValidationArguments.ts | 5 +++++ src/validation/ValidationExecutor.ts | 6 ++++-- src/validation/ValidatorConstraintInterface.ts | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 886712dd76..bc4938edc5 100644 --- a/README.md +++ b/README.md @@ -603,6 +603,8 @@ If you have custom validation logic you can create a _Constraint class_: which defines validation logic. If validation succeeds, method returns true, otherwise false. Custom validator can be asynchronous, if you want to perform validation after some asynchronous operations, simply return a promise with boolean inside in `validate` method. + `ValidationArguments` contains useful information about validation process, like object that is being validated, + or the current object instance to validate value based on parent nodes. Also we defined optional method `defaultMessage` which defines a default error message, in the case that the decorator's implementation doesn't set an error message. diff --git a/src/validation/ValidationArguments.ts b/src/validation/ValidationArguments.ts index 5e760187f2..80b1fed56d 100644 --- a/src/validation/ValidationArguments.ts +++ b/src/validation/ValidationArguments.ts @@ -23,6 +23,11 @@ export interface ValidationArguments { */ object: object; + /** + * Instance of the object being validated. + */ + instance: object; + /** * Name of the object's property being validated. */ diff --git a/src/validation/ValidationExecutor.ts b/src/validation/ValidationExecutor.ts index 838e79f711..587692cf46 100644 --- a/src/validation/ValidationExecutor.ts +++ b/src/validation/ValidationExecutor.ts @@ -269,12 +269,13 @@ export class ValidationExecutor { targetName: object.constructor ? (object.constructor as any).name : undefined, property: metadata.propertyName, object: object, + instance: this.instance, value: value, constraints: metadata.constraints, }; if (!metadata.each || !(Array.isArray(value) || value instanceof Set || value instanceof Map)) { - const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments, this.instance); + const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments); if (isPromise(validatedValue)) { const promise = validatedValue.then(isValid => { if (!isValid) { @@ -303,7 +304,7 @@ export class ValidationExecutor { const arrayValue = convertToArray(value); // Validation needs to be applied to each array item const validatedSubValues = arrayValue.map((subValue: any) => - customConstraintMetadata.instance.validate(subValue, validationArguments, this.instance) + customConstraintMetadata.instance.validate(subValue, validationArguments) ); const validationIsAsync = validatedSubValues.some((validatedSubValue: boolean | Promise) => isPromise(validatedSubValue) @@ -411,6 +412,7 @@ export class ValidationExecutor { property: metadata.propertyName, object: object, value: value, + instance: this.instance, constraints: metadata.constraints, }; diff --git a/src/validation/ValidatorConstraintInterface.ts b/src/validation/ValidatorConstraintInterface.ts index 591b2e5d59..f29ce2f6fe 100644 --- a/src/validation/ValidatorConstraintInterface.ts +++ b/src/validation/ValidatorConstraintInterface.ts @@ -6,7 +6,7 @@ export interface ValidatorConstraintInterface { /** * Method to be called to perform custom validation over given value. */ - validate(value: any, validationArguments?: ValidationArguments, instance?: any): Promise | boolean; + validate(value: any, validationArguments?: ValidationArguments): Promise | boolean; /** * Gets default message when validation for this constraint fail.