Skip to content

Commit 95b2958

Browse files
committed
Validation validateIf
1 parent 77ef375 commit 95b2958

File tree

5 files changed

+137
-8
lines changed

5 files changed

+137
-8
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,51 @@ validate(user, {
566566
There is also a special flag `always: true` in validation options that you can use. This flag says that this validation
567567
must be applied always no matter which group is used.
568568

569+
## Validation validateIf
570+
If you want to validate that condition by object, you can use validation validateIf.
571+
572+
```typescript
573+
class MyClass {
574+
@Min(5, {
575+
message: 'min',
576+
validateIf: (value, args) => {
577+
const obj = args.object as MyClass;
578+
return !obj.someOtherProperty || obj.someOtherProperty === 'min';
579+
}
580+
})
581+
@Max(3, {
582+
message: 'max',
583+
validateIf: (value, args) => {
584+
const obj = args.object as MyClass;
585+
return !obj.someOtherProperty || obj.someOtherProperty === 'max';
586+
}
587+
})
588+
someProperty: number;
589+
590+
someOtherProperty: string;
591+
}
592+
593+
const model = new MyClass();
594+
model.someProperty = 4
595+
model.someOtherProperty = 'min';
596+
validator.validate(model) // this only validate min
597+
598+
const model = new MyClass();
599+
model.someProperty = 4
600+
model.someOtherProperty = 'max';
601+
validator.validate(model) // this only validate max
602+
603+
const model = new MyClass();
604+
model.someProperty = 4
605+
model.someOtherProperty = '';
606+
validator.validate(model) // this validate both
607+
608+
const model = new MyClass();
609+
model.someProperty = 4
610+
model.someOtherProperty = 'other';
611+
validator.validate(model) // this validate none
612+
613+
```
569614
## Custom validation classes
570615

571616
If you have custom validation logic you can create a _Constraint class_:

src/decorator/ValidationOptions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ export interface ValidationOptions {
2929
* A transient set of data passed through to the validation result for response mapping
3030
*/
3131
context?: any;
32+
33+
/**
34+
* validation will be performed while the result is true
35+
*/
36+
validateIf?: (value: any, validationArguments: ValidationArguments) => boolean;
3237
}
3338

3439
export function isValidationOptions(val: any): val is ValidationOptions {
3540
if (!val) {
3641
return false;
3742
}
38-
return 'each' in val || 'message' in val || 'groups' in val || 'always' in val || 'context' in val;
43+
return 'each' in val || 'message' in val || 'groups' in val || 'always' in val || 'context' in val || 'validateIf' in val;
3944
}

src/metadata/ValidationMetadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export class ValidationMetadata {
5959
*/
6060
context?: any = undefined;
6161

62+
/**
63+
* validation will be performed while the result is true
64+
*/
65+
validateIf?: (value: any, validationArguments: ValidationArguments) => boolean;
66+
6267
/**
6368
* Extra options specific to validation type.
6469
*/
@@ -81,6 +86,7 @@ export class ValidationMetadata {
8186
this.always = args.validationOptions.always;
8287
this.each = args.validationOptions.each;
8388
this.context = args.validationOptions.context;
89+
this.validateIf = args.validationOptions.validateIf;
8490
}
8591
}
8692
}

src/validation/ValidationExecutor.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ export class ValidationExecutor {
245245

246246
private customValidations(object: object, value: any, metadatas: ValidationMetadata[], error: ValidationError): void {
247247
metadatas.forEach(metadata => {
248+
const getValidationArguments = () => {
249+
const validationArguments: ValidationArguments = {
250+
targetName: object.constructor ? (object.constructor as any).name : undefined,
251+
property: metadata.propertyName,
252+
object: object,
253+
value: value,
254+
constraints: metadata.constraints,
255+
};
256+
return validationArguments;
257+
}
258+
if (metadata.validateIf) {
259+
const validateIf = metadata.validateIf(object, getValidationArguments());
260+
if (!validateIf) return;
261+
}
248262
this.metadataStorage.getTargetValidatorConstraints(metadata.constraintCls).forEach(customConstraintMetadata => {
249263
if (customConstraintMetadata.async && this.ignoreAsyncValidations) return;
250264
if (
@@ -254,13 +268,7 @@ export class ValidationExecutor {
254268
)
255269
return;
256270

257-
const validationArguments: ValidationArguments = {
258-
targetName: object.constructor ? (object.constructor as any).name : undefined,
259-
property: metadata.propertyName,
260-
object: object,
261-
value: value,
262-
constraints: metadata.constraints,
263-
};
271+
const validationArguments = getValidationArguments();
264272

265273
if (!metadata.each || !(Array.isArray(value) || value instanceof Set || value instanceof Map)) {
266274
const validatedValue = customConstraintMetadata.instance.validate(value, validationArguments);

test/functional/validation-options.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
ValidatorConstraint,
99
IsOptional,
1010
IsNotEmpty,
11+
Min,
12+
Max,
1113
} from '../../src/decorator/decorators';
1214
import { Validator } from '../../src/validation/Validator';
1315
import {
@@ -1215,3 +1217,66 @@ describe('context', () => {
12151217
});
12161218
});
12171219
});
1220+
1221+
1222+
describe('validateIf', () => {
1223+
class MyClass {
1224+
@Min(5, {
1225+
message: 'min',
1226+
validateIf: (value, args) => {
1227+
const obj = args.object as MyClass;
1228+
return !obj.someOtherProperty || obj.someOtherProperty === 'min';
1229+
}
1230+
})
1231+
@Max(3, {
1232+
message: 'max',
1233+
validateIf: (value, args) => {
1234+
const obj = args.object as MyClass;
1235+
return !obj.someOtherProperty || obj.someOtherProperty === 'max';
1236+
}
1237+
})
1238+
someProperty: number;
1239+
1240+
someOtherProperty: string;
1241+
}
1242+
1243+
describe('should validate if validateIf return true.', () => {
1244+
it('should only validate min', () => {
1245+
const model = new MyClass();
1246+
model.someProperty = 4
1247+
model.someOtherProperty = 'min';
1248+
return validator.validate(model).then(errors => {
1249+
expect(errors.length).toEqual(1);
1250+
expect(errors[0].constraints['min']).toBe('min');
1251+
expect(errors[0].constraints['max']).toBe(undefined);
1252+
});
1253+
})
1254+
it('should only validate max', () => {
1255+
const model = new MyClass();
1256+
model.someProperty = 4
1257+
model.someOtherProperty = 'max';
1258+
return validator.validate(model).then(errors => {
1259+
expect(errors.length).toEqual(1);
1260+
expect(errors[0].constraints['min']).toBe(undefined);
1261+
expect(errors[0].constraints['max']).toBe('max');
1262+
});
1263+
})
1264+
it('should validate both', () => {
1265+
const model = new MyClass();
1266+
model.someProperty = 4
1267+
return validator.validate(model).then(errors => {
1268+
expect(errors.length).toEqual(1);
1269+
expect(errors[0].constraints['min']).toBe('min');
1270+
expect(errors[0].constraints['max']).toBe('max');
1271+
});
1272+
})
1273+
it('should validate none', () => {
1274+
const model = new MyClass();
1275+
model.someProperty = 4
1276+
model.someOtherProperty = 'other';
1277+
return validator.validate(model).then(errors => {
1278+
expect(errors.length).toEqual(0);
1279+
});
1280+
})
1281+
});
1282+
})

0 commit comments

Comments
 (0)