Skip to content

Commit 166f530

Browse files
committed
fix inherited property validator overrides
1 parent e4bc6ff commit 166f530

19 files changed

+159
-12
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ Lets create another custom validation decorator called `IsUserAlreadyExist`:
726726
export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
727727
return function (object: Object, propertyName: string) {
728728
registerDecorator({
729+
name: 'IsUserAlreadyExist',
729730
target: object.constructor,
730731
propertyName: propertyName,
731732
options: validationOptions,

sample/sample6-custom-decorator/IsLongerThan.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ValidationArguments } from '../../src/validation/ValidationArguments';
77
export function IsLongerThan(property: string, validationOptions?: ValidationOptions) {
88
return function (object: Object, propertyName: string) {
99
registerDecorator({
10+
name: 'IsLongerThan',
1011
target: object.constructor,
1112
propertyName: propertyName,
1213
options: validationOptions,

src/decorator/common/Allow.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getMetadataStorage } from '../../metadata/MetadataStorage';
1010
export function Allow(validationOptions?: ValidationOptions): PropertyDecorator {
1111
return function (object: object, propertyName: string): void {
1212
const args: ValidationMetadataArgs = {
13+
name: 'Allow',
1314
type: ValidationTypes.WHITELIST,
1415
target: object.constructor,
1516
propertyName: propertyName,

src/decorator/common/IsOptional.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getMetadataStorage } from '../../metadata/MetadataStorage';
1010
export function IsOptional(validationOptions?: ValidationOptions): PropertyDecorator {
1111
return function (object: object, propertyName: string): void {
1212
const args: ValidationMetadataArgs = {
13+
name: 'IsOptional',
1314
type: ValidationTypes.CONDITIONAL_VALIDATION,
1415
target: object.constructor,
1516
propertyName: propertyName,

src/decorator/common/Validate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export function ValidatorConstraint(options?: { name?: string; async?: boolean }
2626
/**
2727
* Performs validation based on the given custom validation class.
2828
* Validation class must be decorated with ValidatorConstraint decorator.
29+
*
30+
* TODO: allow passing in a `name` so the validator instance created can be uniquely identified
31+
* until then, this validator will be overwritten by properties decorated with `Validate` on subclasses
2932
*/
3033
export function Validate(constraintClass: Function, validationOptions?: ValidationOptions): PropertyDecorator;
3134
export function Validate(
@@ -40,6 +43,7 @@ export function Validate(
4043
): PropertyDecorator {
4144
return function (object: object, propertyName: string): void {
4245
const args: ValidationMetadataArgs = {
46+
name: 'Validate',
4347
type: ValidationTypes.CUSTOM_VALIDATION,
4448
target: object.constructor,
4549
propertyName: propertyName,

src/decorator/common/ValidateIf.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import { getMetadataStorage } from '../../metadata/MetadataStorage';
66

77
/**
88
* Ignores the other validators on a property when the provided condition function returns false.
9+
*
10+
* TODO: allow passing in a `name` so the validator instance created can be uniquely identified
11+
* until then, this validator will be overwritten by properties decorated with `Validate` on subclasses
912
*/
1013
export function ValidateIf(
1114
condition: (object: any, value: any) => boolean,
1215
validationOptions?: ValidationOptions
1316
): PropertyDecorator {
1417
return function (object: object, propertyName: string): void {
1518
const args: ValidationMetadataArgs = {
19+
name: 'ValidateIf',
1620
type: ValidationTypes.CONDITIONAL_VALIDATION,
1721
target: object.constructor,
1822
propertyName: propertyName,

src/decorator/common/ValidateNested.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function ValidateNested(validationOptions?: ValidationOptions): PropertyD
1414

1515
return function (object: object, propertyName: string): void {
1616
const args: ValidationMetadataArgs = {
17+
name: 'ValidateNested',
1718
type: ValidationTypes.NESTED_VALIDATION,
1819
target: object.constructor,
1920
propertyName: propertyName,

src/decorator/common/ValidatePromise.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getMetadataStorage } from '../../metadata/MetadataStorage';
1010
export function ValidatePromise(validationOptions?: ValidationOptions): PropertyDecorator {
1111
return function (object: object, propertyName: string): void {
1212
const args: ValidationMetadataArgs = {
13+
name: 'ValidatePromise',
1314
type: ValidationTypes.PROMISE_VALIDATION,
1415
target: object.constructor,
1516
propertyName: propertyName,

src/metadata/MetadataStorage.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,25 @@ export class MetadataStorage {
119119
// filter out duplicate metadatas, prefer original metadatas instead of inherited metadatas
120120
const uniqueInheritedMetadatas = inheritedMetadatas.filter(inheritedMetadata => {
121121
return !originalMetadatas.find(originalMetadata => {
122-
// expect validators to be duplicate if they point to the same validator function
123-
return (
124-
originalMetadata.propertyName === inheritedMetadata.propertyName &&
125-
originalMetadata.constraintCls === inheritedMetadata.constraintCls
126-
);
122+
// We have no clean way to determine if 2 validators are the same, and thus can't easily determine
123+
// which validators have been overwritten by a subclass
124+
// - Can't use `validatorCls` object/function: it's recreated on a per-usage basis so two decorators will give different instances
125+
// - Can't use `ValidationTypes`: this was useable until 11a7b8bb59c83d55bc723ebb236fdca912f49d88,
126+
// after which 90% of ValidationTypes were removed in favour of type "customValidation". Note that
127+
// some validators, including any custom validators, still had type "customValidation" before this, and therefore
128+
// did not work with inherited validation
129+
// - `name`: can be used to uniquely identify a validator, but is optional to not break backwards compatability
130+
// in a future release, it should be made required
131+
const isSameProperty = originalMetadata.propertyName === inheritedMetadata.propertyName;
132+
const isSameValidator =
133+
originalMetadata.name && inheritedMetadata.name
134+
? // TODO: when names becomes required, ONLY compare by name
135+
originalMetadata.name === inheritedMetadata.name
136+
: // 95% of decorators are of type "customValidation", despite being different decorators
137+
// therefore this equality comparison introduces lots of false positives
138+
originalMetadata.type === inheritedMetadata.type;
139+
140+
return isSameProperty && isSameValidator;
127141
});
128142
});
129143

src/metadata/ValidationMetadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export class ValidationMetadata {
99
// Properties
1010
// -------------------------------------------------------------------------
1111

12+
/**
13+
* Validation name. Used to uniquely identify this validator.
14+
*/
15+
name?: string;
16+
1217
/**
1318
* Validation type.
1419
*/
@@ -69,6 +74,7 @@ export class ValidationMetadata {
6974
// -------------------------------------------------------------------------
7075

7176
constructor(args: ValidationMetadataArgs) {
77+
this.name = args.name;
7278
this.type = args.type;
7379
this.target = args.target;
7480
this.propertyName = args.propertyName;

0 commit comments

Comments
 (0)