Skip to content

Commit 0653966

Browse files
NickKelly1NoNameProvided
authored andcommitted
fix inherited property validator overrides
1 parent 0bd3854 commit 0653966

18 files changed

+157
-10
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ Lets create another custom validation decorator called `IsUserAlreadyExist`:
733733
export function IsUserAlreadyExist(validationOptions?: ValidationOptions) {
734734
return function (object: Object, propertyName: string) {
735735
registerDecorator({
736+
name: 'IsUserAlreadyExist',
736737
target: object.constructor,
737738
propertyName: propertyName,
738739
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
@@ -138,11 +138,25 @@ export class MetadataStorage {
138138
// filter out duplicate metadatas, prefer original metadatas instead of inherited metadatas
139139
const uniqueInheritedMetadatas = inheritedMetadatas.filter(inheritedMetadata => {
140140
return !originalMetadatas.find(originalMetadata => {
141-
// expect validators to be duplicate if they point to the same validator function
142-
return (
143-
originalMetadata.propertyName === inheritedMetadata.propertyName &&
144-
originalMetadata.constraintCls === inheritedMetadata.constraintCls
145-
);
141+
// We have no clean way to determine if 2 validators are the same, and thus can't easily determine
142+
// which validators have been overwritten by a subclass
143+
// - Can't use `validatorCls` object/function: it's recreated on a per-usage basis so two decorators will give different instances
144+
// - Can't use `ValidationTypes`: this was useable until 11a7b8bb59c83d55bc723ebb236fdca912f49d88,
145+
// after which 90% of ValidationTypes were removed in favour of type "customValidation". Note that
146+
// some validators, including any custom validators, still had type "customValidation" before this, and therefore
147+
// did not work with inherited validation
148+
// - `name`: can be used to uniquely identify a validator, but is optional to not break backwards compatability
149+
// in a future release, it should be made required
150+
const isSameProperty = originalMetadata.propertyName === inheritedMetadata.propertyName;
151+
const isSameValidator =
152+
originalMetadata.name && inheritedMetadata.name
153+
? // TODO: when names becomes required, ONLY compare by name
154+
originalMetadata.name === inheritedMetadata.name
155+
: // 95% of decorators are of type "customValidation", despite being different decorators
156+
// therefore this equality comparison introduces lots of false positives
157+
originalMetadata.type === inheritedMetadata.type;
158+
159+
return isSameProperty && isSameValidator;
146160
});
147161
});
148162

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
*/
@@ -74,6 +79,7 @@ export class ValidationMetadata {
7479
// -------------------------------------------------------------------------
7580

7681
constructor(args: ValidationMetadataArgs) {
82+
this.name = args.name;
7783
this.type = args.type;
7884
this.name = args.name;
7985
this.target = args.target;

0 commit comments

Comments
 (0)