Nest Pipe

概述

管道是一个具有 @Injectable() 装饰器的类,应实现 PipeTransform 接口。

管道两个经典的使用场景:

  1. 装换: 转换输入的数据到期望的格式
  2. 验证: 评估输入数据的有效性,如果有效,原数据传递下去,否则抛出数据有误的异常

在以上场景,管道操作即将被控制器的路由处理器的处理的 arguments. Nest 在方法调用之前插入一个管道,管道接收到方法的参数并操作它。任何转换及验证操作导致参数的转变,都将被路由处理器调用。

管道运行在异常区域内,意味着异常层可以捕获管道抛出的异常。当一个异常在管道里抛出,控制器将不再执行后续操作。

内置管道

绑定管道

@Get(':id')
async findOne(
  @Param('id', ParseIntPipe) id: number // 传 `ParseIntPipe` 类而非实例,将实例化的责任交给框架,开启依赖注入。
  // @Param('id, new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) 自定义配置
) {
  return this.catService.findOne(id);
}

// GET localhost:3000/abc 将抛出 “Validation failed (numeric string is expected)” 400 响应错误

自定义管道

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
  // value 当前正在处理的方法参数(在路由方法接收之前)
  // metadata 当前正在处理的方法参数的元数据
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

// 以上 metadata 带有如下属性
export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<unknown>,
  data?: string;
}

对象结构验证 (Object schema validation)

Joi 库是允许您使用一个可读的 API 以非常简单的方式创建 schema。

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  cosntructor(private schema: ObjectSchema) {}
  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

// 绑定验证管道
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catService.create(createCatDto);
}

类验证

class-validator 库允许创建基于装饰器的验证。

import { IsString, IsInt } from 'class-validation';
export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  age: number;

  @IsString()
  breed: string;
}

// validation.pipe.ts
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) { // 跳过没有校验装饰器的场景
      return value;
    }
    const object = plainToClass(metatype, value); // 转换普通 JS 对象到类型化的对象,以便执行后续验证
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

// 绑定 `ValidationPipe`,管道可以是参数、方法、控制器或全局范围
@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}

全局管道

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
}

在依赖注入方面,全局管道在模块外注册不能执行插入依赖的操作,通过如下代码解决

@Module({
  providers: [
    provide: APP_PIPE,
    useClass: ValidationPipe,
  ]
})

转换使用场景

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return this.catsService.findOne(id);
}

默认管道参数

Parse* 管道默许定义了参数,如值为 null 或是 undefined 将抛出错误。DefaultValuePipe 给管道参数提供默认值


async findAll(
  @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
  @Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number
) {
  return this.catsService.findAll({ activeOnly, page });
}