Skip to content

Commit 63fdf67

Browse files
committed
feat(common): add new option disableFlattenErrorMessages to validation pipe
1 parent b6ea2a1 commit 63fdf67

File tree

2 files changed

+104
-7
lines changed

2 files changed

+104
-7
lines changed

packages/common/pipes/validation.pipe.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ import { isNil, isUndefined } from '../utils/shared.utils';
2626
export interface ValidationPipeOptions extends ValidatorOptions {
2727
transform?: boolean;
2828
disableErrorMessages?: boolean;
29+
disableFlattenErrorMessages?: boolean;
2930
transformOptions?: ClassTransformOptions;
3031
errorHttpStatusCode?: ErrorHttpStatusCode;
31-
exceptionFactory?: (errors: ValidationError[]) => any;
32+
exceptionFactory?: (errors: ValidationError[] | string[]) => any;
3233
validateCustomDecorators?: boolean;
3334
expectedType?: Type<any>;
3435
validatorPackage?: ValidatorPackage;
@@ -47,18 +48,20 @@ let classTransformer: TransformerPackage = {} as any;
4748
export class ValidationPipe implements PipeTransform<any> {
4849
protected isTransformEnabled: boolean;
4950
protected isDetailedOutputDisabled?: boolean;
51+
protected isFlattenErrorMessagesDisabled?: boolean;
5052
protected validatorOptions: ValidatorOptions;
5153
protected transformOptions: ClassTransformOptions;
5254
protected errorHttpStatusCode: ErrorHttpStatusCode;
5355
protected expectedType: Type<any>;
54-
protected exceptionFactory: (errors: ValidationError[]) => any;
56+
protected exceptionFactory: (errors: ValidationError[] | string[]) => any;
5557
protected validateCustomDecorators: boolean;
5658

5759
constructor(@Optional() options?: ValidationPipeOptions) {
5860
options = options || {};
5961
const {
6062
transform,
6163
disableErrorMessages,
64+
disableFlattenErrorMessages,
6265
errorHttpStatusCode,
6366
expectedType,
6467
transformOptions,
@@ -72,6 +75,7 @@ export class ValidationPipe implements PipeTransform<any> {
7275
this.isTransformEnabled = !!transform;
7376
this.transformOptions = transformOptions;
7477
this.isDetailedOutputDisabled = disableErrorMessages;
78+
this.isFlattenErrorMessagesDisabled = disableFlattenErrorMessages;
7579
this.validateCustomDecorators = validateCustomDecorators || false;
7680
this.errorHttpStatusCode = errorHttpStatusCode || HttpStatus.BAD_REQUEST;
7781
this.expectedType = expectedType;
@@ -142,7 +146,11 @@ export class ValidationPipe implements PipeTransform<any> {
142146

143147
const errors = await this.validate(entity, this.validatorOptions);
144148
if (errors.length > 0) {
145-
throw await this.exceptionFactory(errors);
149+
let validationErrors: ValidationError[] | string[] = errors;
150+
if (!this.isFlattenErrorMessagesDisabled) {
151+
validationErrors = this.flattenValidationErrors(errors);
152+
}
153+
throw await this.exceptionFactory(validationErrors);
146154
}
147155
if (isPrimitive) {
148156
// if the value is a primitive value and the validation process has been successfully completed
@@ -166,12 +174,11 @@ export class ValidationPipe implements PipeTransform<any> {
166174
}
167175

168176
public createExceptionFactory() {
169-
return (validationErrors: ValidationError[] = []) => {
177+
return (validationErrors: ValidationError[] | string[] = []) => {
170178
if (this.isDetailedOutputDisabled) {
171179
return new HttpErrorByCode[this.errorHttpStatusCode]();
172180
}
173-
const errors = this.flattenValidationErrors(validationErrors);
174-
return new HttpErrorByCode[this.errorHttpStatusCode](errors);
181+
return new HttpErrorByCode[this.errorHttpStatusCode](validationErrors);
175182
};
176183
}
177184

packages/common/test/pipes/validation.pipe.spec.ts

+91-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ import {
99
IsOptional,
1010
IsString,
1111
ValidateNested,
12+
ValidationError,
1213
} from 'class-validator';
1314
import { HttpStatus } from '../../enums';
14-
import { UnprocessableEntityException } from '../../exceptions';
15+
import { HttpException, UnprocessableEntityException } from '../../exceptions';
1516
import { ArgumentMetadata } from '../../interfaces';
1617
import { ValidationPipe } from '../../pipes/validation.pipe';
1718
chai.use(chaiAsPromised);
1819

20+
class CustomTestError extends HttpException {
21+
constructor(errors: any) {
22+
super(errors, 418);
23+
}
24+
}
25+
1926
@Exclude()
2027
class TestModelInternal {
2128
constructor() {}
@@ -585,4 +592,87 @@ describe('ValidationPipe', () => {
585592
expect(await target.transform(testObj, m)).to.deep.equal(testObj);
586593
});
587594
});
595+
596+
describe('option: "exceptionFactory"', () => {
597+
describe('when validation fails', () => {
598+
beforeEach(() => {
599+
target = new ValidationPipe({
600+
exceptionFactory: (errors) => new CustomTestError(errors),
601+
});
602+
});
603+
it('should throw a CustomTestError exception', async () => {
604+
const testObj = { prop1: 'value1' };
605+
try {
606+
await target.transform(testObj, metadata);
607+
} catch (err) {
608+
expect(err).to.be.instanceOf(CustomTestError);
609+
}
610+
});
611+
});
612+
});
613+
614+
describe('option: "disableFlattenErrorMessages"', () => {
615+
describe('when disableFlattenErrorMessages is true', () => {
616+
beforeEach(() => {
617+
target = new ValidationPipe({
618+
disableFlattenErrorMessages: true,
619+
exceptionFactory: (errors) => new CustomTestError(errors),
620+
});
621+
});
622+
it('should throw a CustomTestError without flatten errors', async () => {
623+
const testObj = { prop1: 'value1' };
624+
try {
625+
await target.transform(testObj, metadata);
626+
} catch (err) {
627+
expect(err).to.be.instanceOf(CustomTestError);
628+
expect(err.getResponse()).to.be.an('array')
629+
err.getResponse().forEach((error: any) => {
630+
expect(error).to.be.instanceOf(ValidationError);
631+
});
632+
}
633+
});
634+
});
635+
636+
describe('when disableFlattenErrorMessages is false', () => {
637+
beforeEach(() => {
638+
target = new ValidationPipe({
639+
disableFlattenErrorMessages: false,
640+
exceptionFactory: (errors) => new CustomTestError(errors),
641+
});
642+
});
643+
it('should throw a CustomTestError with flatten errors', async () => {
644+
const testObj = { prop1: 'value1' };
645+
try {
646+
await target.transform(testObj, metadata);
647+
} catch (err) {
648+
expect(err).to.be.instanceOf(CustomTestError);
649+
expect(err.getResponse()).to.be.an('array')
650+
err.getResponse().forEach((error: any) => {
651+
expect(error).to.be.a('string');
652+
});
653+
}
654+
});
655+
});
656+
657+
describe('when disableFlattenErrorMessages is not set', () => {
658+
beforeEach(() => {
659+
target = new ValidationPipe({
660+
disableFlattenErrorMessages: false,
661+
exceptionFactory: (errors) => new CustomTestError(errors),
662+
});
663+
});
664+
it('should throw a CustomTestError with flatten errors', async () => {
665+
const testObj = { prop1: 'value1' };
666+
try {
667+
await target.transform(testObj, metadata);
668+
} catch (err) {
669+
expect(err).to.be.instanceOf(CustomTestError);
670+
expect(err.getResponse()).to.be.an('array')
671+
err.getResponse().forEach((error: any) => {
672+
expect(error).to.be.a('string');
673+
});
674+
}
675+
});
676+
});
677+
});
588678
});

0 commit comments

Comments
 (0)