Skip to content

Commit

Permalink
feat(common): add new option disableFlattenErrorMessages to validatio…
Browse files Browse the repository at this point in the history
…n pipe
  • Loading branch information
civilcoder55 committed Jan 3, 2025
1 parent b6ea2a1 commit 63fdf67
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 7 deletions.
19 changes: 13 additions & 6 deletions packages/common/pipes/validation.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import { isNil, isUndefined } from '../utils/shared.utils';
export interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
disableErrorMessages?: boolean;
disableFlattenErrorMessages?: boolean;
transformOptions?: ClassTransformOptions;
errorHttpStatusCode?: ErrorHttpStatusCode;
exceptionFactory?: (errors: ValidationError[]) => any;
exceptionFactory?: (errors: ValidationError[] | string[]) => any;
validateCustomDecorators?: boolean;
expectedType?: Type<any>;
validatorPackage?: ValidatorPackage;
Expand All @@ -47,18 +48,20 @@ let classTransformer: TransformerPackage = {} as any;
export class ValidationPipe implements PipeTransform<any> {
protected isTransformEnabled: boolean;
protected isDetailedOutputDisabled?: boolean;
protected isFlattenErrorMessagesDisabled?: boolean;
protected validatorOptions: ValidatorOptions;
protected transformOptions: ClassTransformOptions;
protected errorHttpStatusCode: ErrorHttpStatusCode;
protected expectedType: Type<any>;
protected exceptionFactory: (errors: ValidationError[]) => any;
protected exceptionFactory: (errors: ValidationError[] | string[]) => any;
protected validateCustomDecorators: boolean;

constructor(@Optional() options?: ValidationPipeOptions) {
options = options || {};
const {
transform,
disableErrorMessages,
disableFlattenErrorMessages,
errorHttpStatusCode,
expectedType,
transformOptions,
Expand All @@ -72,6 +75,7 @@ export class ValidationPipe implements PipeTransform<any> {
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.isDetailedOutputDisabled = disableErrorMessages;
this.isFlattenErrorMessagesDisabled = disableFlattenErrorMessages;
this.validateCustomDecorators = validateCustomDecorators || false;
this.errorHttpStatusCode = errorHttpStatusCode || HttpStatus.BAD_REQUEST;
this.expectedType = expectedType;
Expand Down Expand Up @@ -142,7 +146,11 @@ export class ValidationPipe implements PipeTransform<any> {

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

public createExceptionFactory() {
return (validationErrors: ValidationError[] = []) => {
return (validationErrors: ValidationError[] | string[] = []) => {
if (this.isDetailedOutputDisabled) {
return new HttpErrorByCode[this.errorHttpStatusCode]();
}
const errors = this.flattenValidationErrors(validationErrors);
return new HttpErrorByCode[this.errorHttpStatusCode](errors);
return new HttpErrorByCode[this.errorHttpStatusCode](validationErrors);
};
}

Expand Down
92 changes: 91 additions & 1 deletion packages/common/test/pipes/validation.pipe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ import {
IsOptional,
IsString,
ValidateNested,
ValidationError,
} from 'class-validator';
import { HttpStatus } from '../../enums';
import { UnprocessableEntityException } from '../../exceptions';
import { HttpException, UnprocessableEntityException } from '../../exceptions';
import { ArgumentMetadata } from '../../interfaces';
import { ValidationPipe } from '../../pipes/validation.pipe';
chai.use(chaiAsPromised);

class CustomTestError extends HttpException {
constructor(errors: any) {
super(errors, 418);
}
}

@Exclude()
class TestModelInternal {
constructor() {}
Expand Down Expand Up @@ -585,4 +592,87 @@ describe('ValidationPipe', () => {
expect(await target.transform(testObj, m)).to.deep.equal(testObj);
});
});

describe('option: "exceptionFactory"', () => {
describe('when validation fails', () => {
beforeEach(() => {
target = new ValidationPipe({
exceptionFactory: (errors) => new CustomTestError(errors),
});
});
it('should throw a CustomTestError exception', async () => {
const testObj = { prop1: 'value1' };
try {
await target.transform(testObj, metadata);
} catch (err) {
expect(err).to.be.instanceOf(CustomTestError);
}
});
});
});

describe('option: "disableFlattenErrorMessages"', () => {
describe('when disableFlattenErrorMessages is true', () => {
beforeEach(() => {
target = new ValidationPipe({
disableFlattenErrorMessages: true,
exceptionFactory: (errors) => new CustomTestError(errors),
});
});
it('should throw a CustomTestError without flatten errors', async () => {
const testObj = { prop1: 'value1' };
try {
await target.transform(testObj, metadata);
} catch (err) {
expect(err).to.be.instanceOf(CustomTestError);
expect(err.getResponse()).to.be.an('array')
err.getResponse().forEach((error: any) => {
expect(error).to.be.instanceOf(ValidationError);
});
}
});
});

describe('when disableFlattenErrorMessages is false', () => {
beforeEach(() => {
target = new ValidationPipe({
disableFlattenErrorMessages: false,
exceptionFactory: (errors) => new CustomTestError(errors),
});
});
it('should throw a CustomTestError with flatten errors', async () => {
const testObj = { prop1: 'value1' };
try {
await target.transform(testObj, metadata);
} catch (err) {
expect(err).to.be.instanceOf(CustomTestError);
expect(err.getResponse()).to.be.an('array')
err.getResponse().forEach((error: any) => {
expect(error).to.be.a('string');
});
}
});
});

describe('when disableFlattenErrorMessages is not set', () => {
beforeEach(() => {
target = new ValidationPipe({
disableFlattenErrorMessages: false,
exceptionFactory: (errors) => new CustomTestError(errors),
});
});
it('should throw a CustomTestError with flatten errors', async () => {
const testObj = { prop1: 'value1' };
try {
await target.transform(testObj, metadata);
} catch (err) {
expect(err).to.be.instanceOf(CustomTestError);
expect(err.getResponse()).to.be.an('array')
err.getResponse().forEach((error: any) => {
expect(error).to.be.a('string');
});
}
});
});
});
});

0 comments on commit 63fdf67

Please sign in to comment.