Skip to content

Commit 54a2cdf

Browse files
merge: enable forbidUnknownValues by default (#1798)
2 parents 23071f6 + 0e84a27 commit 54a2cdf

File tree

4 files changed

+50
-19
lines changed

4 files changed

+50
-19
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ export interface ValidatorOptions {
140140
}
141141
```
142142

143-
> It's highly advised to set `forbidUnknownValues: true` as it will prevent unknown objects from passing validation.
143+
> **IMPORTANT**
144+
> The `forbidUnknownValues` value is set to `true` by default and **it is highly advised to keep the default**.
145+
> Setting it to `false` will result unknown objects passing the validation!
144146
145147
## Validation errors
146148

@@ -524,6 +526,10 @@ for you, even if skipMissingProperties is set to true. For such cases you should
524526
In different situations you may want to use different validation schemas of the same object.
525527
In such cases you can use validation groups.
526528

529+
> **IMPORTANT**
530+
> Calling a validation with a group combination that would not result in a validation (eg: non existent group name)
531+
> will result in a unknown value error. When validating with groups the provided group combination should match at least one decorator.
532+
527533
```typescript
528534
import { validate, Min, Length } from 'class-validator';
529535

src/validation/ValidationExecutor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export class ValidationExecutor {
5252
const groups = this.validatorOptions ? this.validatorOptions.groups : undefined;
5353
const strictGroups = (this.validatorOptions && this.validatorOptions.strictGroups) || false;
5454
const always = (this.validatorOptions && this.validatorOptions.always) || false;
55+
/** Forbid unknown values are turned on by default and any other value than false will enable it. */
56+
const forbidUnknownValues =
57+
this.validatorOptions?.forbidUnknownValues === undefined || this.validatorOptions.forbidUnknownValues !== false;
5558

5659
const targetMetadatas = this.metadataStorage.getTargetValidationMetadatas(
5760
object.constructor,
@@ -62,7 +65,7 @@ export class ValidationExecutor {
6265
);
6366
const groupedMetadatas = this.metadataStorage.groupByPropertyName(targetMetadatas);
6467

65-
if (this.validatorOptions && this.validatorOptions.forbidUnknownValues && !targetMetadatas.length) {
68+
if (this.validatorOptions && forbidUnknownValues && !targetMetadatas.length) {
6669
const validationError = new ValidationError();
6770

6871
if (

test/functional/validation-options.spec.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ValidatorConstraint,
1010
IsOptional,
1111
IsNotEmpty,
12+
Allow,
1213
} from '../../src/decorator/decorators';
1314
import { Validator } from '../../src/validation/Validator';
1415
import {
@@ -937,36 +938,45 @@ describe('groups', () => {
937938
});
938939

939940
describe('strictGroups', function () {
940-
class MyClass {
941-
@Contains('hello', {
942-
groups: ['A'],
943-
})
941+
class MyPayload {
942+
/**
943+
* Since forbidUnknownValues defaults to true, we must add a property to
944+
* register the class in the metadata storage. Otherwise the unknown value check
945+
* would take priority (first check) and exit without running the grouping logic.
946+
*
947+
* To solve this we register this property with always: true, so at least a single
948+
* metadata is returned for each validation preventing the unknown value was passed error.
949+
*/
950+
@IsOptional({ always: true })
951+
propertyToRegisterClass: string;
952+
953+
@Contains('hello', { groups: ['A'] })
944954
title: string;
945955
}
946956

947-
const model1 = new MyClass();
957+
const instance = new MyPayload();
948958

949959
it('should ignore decorators with groups if validating without groups', function () {
950-
return validator.validate(model1, { strictGroups: true }).then(errors => {
960+
return validator.validate(instance, { strictGroups: true }).then(errors => {
951961
expect(errors).toHaveLength(0);
952962
});
953963
});
954964

955965
it('should ignore decorators with groups if validating with empty groups array', function () {
956-
return validator.validate(model1, { strictGroups: true, groups: [] }).then(errors => {
966+
return validator.validate(instance, { strictGroups: true, groups: [] }).then(errors => {
957967
expect(errors).toHaveLength(0);
958968
});
959969
});
960970

961971
it('should include decorators with groups if validating with matching groups', function () {
962-
return validator.validate(model1, { strictGroups: true, groups: ['A'] }).then(errors => {
972+
return validator.validate(instance, { strictGroups: true, groups: ['A'] }).then(errors => {
963973
expect(errors).toHaveLength(1);
964974
expectTitleContains(errors[0]);
965975
});
966976
});
967977

968978
it('should not include decorators with groups if validating with different groups', function () {
969-
return validator.validate(model1, { strictGroups: true, groups: ['B'] }).then(errors => {
979+
return validator.validate(instance, { strictGroups: true, groups: ['B'] }).then(errors => {
970980
expect(errors).toHaveLength(0);
971981
});
972982
});

test/functional/whitelist-validation.spec.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Allow, IsDefined, Min } from '../../src/decorator/decorators';
1+
import { Allow, IsDefined, IsOptional, Min } from '../../src/decorator/decorators';
22
import { Validator } from '../../src/validation/Validator';
33
import { ValidationTypes } from '../../src';
44

@@ -46,18 +46,30 @@ describe('whitelist validation', () => {
4646
});
4747

4848
it('should throw an error when forbidNonWhitelisted flag is set', () => {
49-
class MyClass {}
49+
class MyPayload {
50+
/**
51+
* Since forbidUnknownValues defaults to true, we must add a property to
52+
* register the class in the metadata storage. Otherwise the unknown value check
53+
* would take priority (first check) and exit without running the whitelist logic.
54+
*/
55+
@IsOptional()
56+
propertyToRegisterClass: string;
5057

51-
const model: any = new MyClass();
58+
nonDecorated: string;
5259

53-
model.unallowedProperty = 'non-whitelisted';
60+
constructor(nonDecorated: string) {
61+
this.nonDecorated = nonDecorated;
62+
}
63+
}
64+
65+
const instance = new MyPayload('non-whitelisted');
5466

55-
return validator.validate(model, { whitelist: true, forbidNonWhitelisted: true }).then(errors => {
67+
return validator.validate(instance, { whitelist: true, forbidNonWhitelisted: true }).then(errors => {
5668
expect(errors.length).toEqual(1);
57-
expect(errors[0].target).toEqual(model);
58-
expect(errors[0].property).toEqual('unallowedProperty');
69+
expect(errors[0].target).toEqual(instance);
70+
expect(errors[0].property).toEqual('nonDecorated');
5971
expect(errors[0].constraints).toHaveProperty(ValidationTypes.WHITELIST);
60-
expect(() => errors[0].toString()).not.toThrowError();
72+
expect(() => errors[0].toString()).not.toThrow();
6173
});
6274
});
6375
});

0 commit comments

Comments
 (0)