Skip to content

Commit 26843cd

Browse files
feature(@nestjs/common) validation pipe
2 parents aaa5fd1 + 339a335 commit 26843cd

6 files changed

Lines changed: 83 additions & 0 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
"@nestjs/microservices": "^4.0.0",
2525
"@nestjs/testing": "^4.0.0",
2626
"@nestjs/websockets": "^4.0.0",
27+
"class-transformer": "^0.1.8",
28+
"class-validator": "^0.7.3",
2729
"cli-color": "^1.1.0",
2830
"engine.io-client": "^3.1.1",
2931
"express": "^4.16.2",

src/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ export {
2929
WsExceptionFilter,
3030
NestInterceptor,
3131
} from './interfaces';
32+
export * from './pipes';
3233
export * from './services/logger.service';

src/common/pipes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './validation.pipe';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { HttpException } from '@nestjs/core';
2+
import {
3+
PipeTransform,
4+
Pipe,
5+
ArgumentMetadata,
6+
HttpStatus,
7+
} from '@nestjs/common';
8+
import { validate } from 'class-validator';
9+
import { plainToClass } from 'class-transformer';
10+
11+
@Pipe()
12+
export class ValidationPipe implements PipeTransform<any> {
13+
public async transform(value, metadata: ArgumentMetadata) {
14+
const { metatype } = metadata;
15+
if (!metatype || !this.toValidate(metatype)) {
16+
return value;
17+
}
18+
const entity = plainToClass(metatype, value);
19+
const errors = await validate(entity);
20+
if (errors.length > 0) {
21+
throw new HttpException('Validation failed', HttpStatus.BAD_REQUEST);
22+
}
23+
return entity;
24+
}
25+
26+
private toValidate(metatype): boolean {
27+
const types = [String, Boolean, Number, Array, Object];
28+
return !types.find(type => metatype === type);
29+
}
30+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as sinon from 'sinon';
2+
import { expect } from 'chai';
3+
import { ArgumentMetadata } from './../../interfaces';
4+
import { IsString } from 'class-validator';
5+
import { ValidationPipe } from './../../pipes/validation.pipe';
6+
7+
class TestModel {
8+
constructor() {}
9+
@IsString() public prop1: string;
10+
11+
@IsString() public prop2: string;
12+
}
13+
14+
describe('ValidationPipe', () => {
15+
let target: ValidationPipe;
16+
const metadata: ArgumentMetadata = {
17+
type: 'body',
18+
metatype: TestModel,
19+
data: '',
20+
};
21+
beforeEach(() => {
22+
target = new ValidationPipe();
23+
});
24+
describe('transform', () => {
25+
describe('when metadata is empty or undefined', () => {
26+
it('should return the value unchanged', async () => {
27+
const testObj = { prop1: 'value1', prop2: 'value2' };
28+
expect(await target.transform(testObj, {} as any)).to.equal(testObj);
29+
expect(await target.transform(testObj, {} as any)).to.not.be.instanceOf(
30+
TestModel,
31+
);
32+
});
33+
});
34+
describe('when metadata contains a class', () => {
35+
it('should return an instance of the class', async () => {
36+
const testObj = { prop1: 'value1', prop2: 'value2' };
37+
const result = await target.transform(testObj, metadata);
38+
expect(result).to.be.instanceOf(TestModel);
39+
});
40+
});
41+
describe('when validation vails', () => {
42+
it('should throw an error', async () => {
43+
const testObj = { prop1: 'value1' };
44+
return expect(target.transform(testObj, metadata)).to.be.rejected;
45+
});
46+
});
47+
});
48+
});

tslint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
true,
1313
"single"
1414
],
15+
"indent": false,
1516
"ordered-imports": [
1617
false
1718
],

0 commit comments

Comments
 (0)