NestJS中的Pipe主要用于处理输入数据的验证和转换。当你使用类验证器(Class Validator)和数据传输对象(DTO)时,Pipe会根据DTO定义的装饰器来校验数据。让我们从源码的角度仔细分析这个过程。
首先,你会创建一个DTO类,并使用Class Validator提供的装饰器来注明验证规则。例如:
// create-user.dto.ts
import { IsString, Length, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsString({ message: '用户名必须是一个字符串' })
@Length(3, 20, { message: '用户名长度必须在3到20个字符之间' })
@IsNotEmpty({ message: '账户不能为空' })
username: string;
@IsString({ message: '密码必须是一个字符串' })
@IsNotEmpty({ message: '密码不能为空' })
@Length(3, 20,{ message: '密码长度必须在3到20个字符之间' })
password: string;
}
在控制器中,你会使用@Body()
装饰器结合ValidationPipe
来应用这些验证规则:
import { Body, Controller, Post } from '@nestjs/common';
import { CreateCatDto } from './create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post('register')
async register(@Body(ValidationPipe) createUserDto: CreateUserDto) {
return this.authService.register(createUserDto.username, createUserDto.password);
}
}
ValidationPipe
的工作方式ValidationPipe
是NestJS中的一个内置管道,它会自动实例化你的DTO,并使用Class Validator库来验证DTO属性。
从源码角度来看,当请求到达带有@Body(ValidationPipe)
的控制器方法时,以下步骤会发生:
// @nestjs\core\router\router-execution-context.js
class RouterExecutionContext {
...
create(instance, callback, methodName, moduleKey, requestMethod, contextId = constants_2.STATIC_CONTEXT, inquirerId) {
...
// 获取函数的参数类型并保存
const { argsLength, fnHandleResponse, paramtypes, getParamsMetadata, httpStatusCode, responseHeaders, hasCustomHeaders, } = this.getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType);
...
}
getMetadata(instance, callback, methodName, moduleKey, requestMethod, contextType) {
const cacheMetadata = this.handlerMetadataStorage.get(instance, methodName);
if (cacheMetadata) {
return cacheMetadata;
}
const metadata = this.contextUtils.reflectCallbackMetadata(instance, methodName, constants_1.ROUTE_ARGS_METADATA) || {};
const keys = Object.keys(metadata);
const argsLength = this.contextUtils.getArgumentsLength(keys, metadata);
const paramtypes = this.contextUtils.reflectCallbackParamtypes(instance, methodName);
const contextFactory = this.contextUtils.getContextFactory(contextType, instance, callback);
const getParamsMetadata = (moduleKey, contextId = constants_2.STATIC_CONTEXT, inquirerId) => this.exchangeKeysForValues(keys, metadata, moduleKey, contextId, inquirerId, contextFactory);
const paramsMetadata = getParamsMetadata(moduleKey);
const isResponseHandled = this.isResponseHandled(instance, methodName, paramsMetadata);
const httpRedirectResponse = this.reflectRedirect(callback);
const fnHandleResponse = this.createHandleResponseFn(callback, isResponseHandled, httpRedirectResponse);
const httpCode = this.reflectHttpStatusCode(callback);
const httpStatusCode = httpCode
? httpCode
: this.responseController.getStatusByMethod(requestMethod);
const responseHeaders = this.reflectResponseHeaders(callback);
const hasCustomHeaders = !(0, shared_utils_1.isEmpty)(responseHeaders);
const handlerMetadata = {
argsLength,
fnHandleResponse,
paramtypes,
getParamsMetadata,
httpStatusCode,
hasCustomHeaders,
responseHeaders,
};
// 保存函数的参数类型
this.handlerMetadataStorage.set(instance, methodName, handlerMetadata);
return handlerMetadata;
}
...
}
exports.RouterExecutionContext = RouterExecutionContext;
ValidationPipe
使用Class Validator库对DTO实例进行验证。这包括检查是否所有装饰器定义的规则都被满足。 // @nestjs\core\router\router-execution-context.js
...
// 路由执行pipe,并传入参数类型和req的值
async getParamValue(value, { metatype, type, data, }, pipes) {
if (!(0, shared_utils_1.isEmpty)(pipes)) {
return this.pipesConsumer.apply(value, { metatype, type, data }, pipes);
}
return value;
}
...
PipesConsumer
执行pipe
的transform
方法。
// 执行applyPipes方法调用pipe的transform
class PipesConsumer {
constructor() {
this.paramsTokenFactory = new params_token_factory_1.ParamsTokenFactory();
}
async apply(value, { metatype, type, data }, pipes) {
const token = this.paramsTokenFactory.exchangeEnumForString(type);
return this.applyPipes(value, { metatype, type: token, data }, pipes);
}
async applyPipes(value, { metatype, type, data }, transforms) {
return transforms.reduce(async (deferredValue, pipe) => {
const val = await deferredValue;
const result = pipe.transform(val, { metatype, type, data });
return result;
}, Promise.resolve(value));
}
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationPipe = void 0;
const tslib_1 = require("tslib");
const iterare_1 = require("iterare");
const util_1 = require("util");
const decorators_1 = require("../decorators");
const core_1 = require("../decorators/core");
const http_status_enum_1 = require("../enums/http-status.enum");
const http_error_by_code_util_1 = require("../utils/http-error-by-code.util");
const load_package_util_1 = require("../utils/load-package.util");
const shared_utils_1 = require("../utils/shared.utils");
let classValidator = {};
let classTransformer = {};
/**
* @see [Validation](https://docs.nestjs.com/techniques/validation)
*
* @publicApi
*/
let ValidationPipe = class ValidationPipe {
constructor(options) {
options = options || {};
const { transform, disableErrorMessages, errorHttpStatusCode, expectedType, transformOptions, validateCustomDecorators, ...validatorOptions } = options;
// @see https://github.com/nestjs/nest/issues/10683#issuecomment-1413690508
this.validatorOptions = { forbidUnknownValues: false, ...validatorOptions };
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.isDetailedOutputDisabled = disableErrorMessages;
this.validateCustomDecorators = validateCustomDecorators || false;
this.errorHttpStatusCode = errorHttpStatusCode || http_status_enum_1.HttpStatus.BAD_REQUEST;
this.expectedType = expectedType;
this.exceptionFactory =
options.exceptionFactory || this.createExceptionFactory();
classValidator = this.loadValidator(options.validatorPackage);
classTransformer = this.loadTransformer(options.transformerPackage);
}
loadValidator(validatorPackage) {
return (validatorPackage ??
(0, load_package_util_1.loadPackage)('class-validator', 'ValidationPipe', () => require('class-validator')));
}
loadTransformer(transformerPackage) {
return (transformerPackage ??
(0, load_package_util_1.loadPackage)('class-transformer', 'ValidationPipe', () => require('class-transformer')));
}
async transform(value, metadata) {
if (this.expectedType) {
metadata = { ...metadata, metatype: this.expectedType };
}
const metatype = metadata.metatype;
if (!metatype || !this.toValidate(metadata)) {
return this.isTransformEnabled
? this.transformPrimitive(value, metadata)
: value;
}
const originalValue = value;
value = this.toEmptyIfNil(value);
const isNil = value !== originalValue;
const isPrimitive = this.isPrimitive(value);
this.stripProtoKeys(value);
// 使用我们定义的的dto将value的值{xxx:xxx,xxx:xxx}实例化
let entity = classTransformer.plainToClass(metatype, value, this.transformOptions);
const originalEntity = entity;
const isCtorNotEqual = entity.constructor !== metatype;
if (isCtorNotEqual && !isPrimitive) {
entity.constructor = metatype;
}
else if (isCtorNotEqual) {
// when "entity" is a primitive value, we have to temporarily
// replace the entity to perform the validation against the original
// metatype defined inside the handler
entity = { constructor: metatype };
}
// 调用class-validator去验证并返回errors
const errors = await this.validate(entity, this.validatorOptions);
if (errors.length > 0) {
throw await this.exceptionFactory(errors);
}
...
}
validate(object, validatorOptions) {
return classValidator.validate(object, validatorOptions);
}
...
};
exports.ValidationPipe = ValidationPipe;
在NestJS的源码中,ValidationPipe
的实现可以在@nestjs/common
包中找到。关键的部分是transform
方法,它负责处理传入的数据,并应用Class Validator的验证逻辑。
NestJS允许你自定义ValidationPipe
的行为,例如自定义错误响应或启用更复杂的验证规则。你可以通过传递选项到ValidationPipe
的构造函数来实现这些自定义行为。
通过这种方式,NestJS中的Pipes提供了一种强大且灵活的方法来确保控制器接收到有效且正确类型的数据。