验证不适用于 Partial<DTO> - NestJS

Has*_*rki 5 javascript api server-side-validation class-validator nestjs

我想在我的 CRUD API 上应用服务器端验证。有问题的实体称为Employee。我正在使用employee.dto(如下所示)作为创建和更新端点。

class-validator 包在该create方法上运行良好,但当我Partial<EmployeeDTO>在 update 方法中使用它时会忽略 DTO 中的所有规则。

请使用下面的代码作为参考。

套餐

"class-transformer": "^0.2.3",
"class-validator": "^0.10.0",
Run Code Online (Sandbox Code Playgroud)

员工 DTO

import { IsString, IsNotEmpty, IsEmail, IsEnum } from 'class-validator';

import { EmployeeRoles } from '../../entities/employee.entity';

export class EmployeeDTO {
  @IsString()
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @IsString()
  @IsNotEmpty()
  password: string;

  @IsString()
  @IsNotEmpty()
  username: string;

  @IsString()
  @IsNotEmpty()
  fullName: string;

  @IsString()
  @IsNotEmpty()
  @IsEnum(EmployeeRoles)
  role: string;
}
Run Code Online (Sandbox Code Playgroud)

员工控制员

import {
  Controller,
  Param,
  Post,
  Body,
  Put,
  UsePipes,
} from '@nestjs/common';

import { EmployeeDTO } from './dto/employee.dto';
import { EmployeeService } from './employee.service';
import { ValidationPipe } from '../shared/pipes/validation.pipe';

@Controller('employee')
export class EmployeeController {
  constructor(private employeeService: EmployeeService) {}

  @Post()
  @UsePipes(ValidationPipe)
  addNewEmployee(@Body() data: EmployeeDTO) {
    return this.employeeService.create(data);
  }

  @Put(':id')
  @UsePipes(ValidationPipe)
  updateEmployee(@Param('id') id: number, @Body() data: Partial<EmployeeDTO>) {
    return this.employeeService.update(id, data);
  }
}
Run Code Online (Sandbox Code Playgroud)

可能的解决方案

我可以想到的解决方法是为createupdate方法创建单独的 DTO ,但我不喜欢重复代码的想法。

Thi*_*ger 5

对于这个答案,我将进行猜测并假设您使用NestJS 文档中ValidationPipe提供的或近似派生的。

您的updateEmployee方法的参数data类型是Partial,它不会发出任何类型元数据。用于ValidationPipe使用实例化class-transformer模块,导致中class-validator模块来验证一个普通的对象,而不是一个EmployeeDTO

为了验证工作,data参数的类型应该是一个类。您可以创建单独的 DTO 来创建和更新您的实体,或者如果您想保留一个类,则使用验证组


Mil*_*zym 5

为了实现部分验证,您可以使用PartialType效用函数。你可以在这里阅读:https : //docs.nestjs.com/openapi/mapped-types#partial

您需要创建另一个类:

export class UpdateEmployeeDTO extends PartialType(EmployeeDTO) {}
Run Code Online (Sandbox Code Playgroud)

然后在你的控制器,你需要更换的类型@Body data Partial<EmployeeDTO>UpdateEmployeeDto。它应该是这样的:

@Patch(':id')
@UsePipes(ValidationPipe)
updateEmployee(@Param('id') id: number, @Body() data: UpdateEmployeeDTO) {
    return this.employeeService.update(id, data);
}
Run Code Online (Sandbox Code Playgroud)

请记住,你应该导入PartialType来自@nestjs/mapped-types@nestjs/swagger一样的文档中建议。可以在此处找到有关此的更多信息

  • 您能否在答案中添加“PartialType”需要从“@nestjs/mapped-types”导入?该文档具有误导性。另外,“updateEmployee”函数应该是 PATCH 而不是 PUT。PUT 旨在替换所有内容,而 PATCH 旨在仅更新特定值。 (3认同)

jos*_*ell 5

我遇到了这个问题,想分享一些发现和解决方案。

让我们假设我们有这个类:

User {
  @IsNotEmpty()
  name: string;
  @IsOptional()
  nickname?: string;
}
Run Code Online (Sandbox Code Playgroud)

在 POST 上,您希望应用所有验证。在 PATCH(或 PUT)上,您只希望验证应用于指定的属性。好了,POST没问题,在控制器中做:

@Body() params: User
Run Code Online (Sandbox Code Playgroud)

但是,PATCH(或 PUT)是有问题的,因为如果不普遍应用它,就无法处理开箱即用的部分验证。

以下是我探索的一些解决方案:

解决方案 1:PartialType [失败]

import { OmitType, PartialType } from '@nestjs/swagger';

UpdateUser extends PartialType(User) {}
Run Code Online (Sandbox Code Playgroud)

然后,在控制器中:

@Body() params: UpdateUser,
Run Code Online (Sandbox Code Playgroud)

使用 NestJS 的 PartialType 不会覆盖父类上的类验证器注释。因此,如果您@IsNotEmpty()在父类上有一个,它仍然会成为必需的。

解决方案 2:部分 [失败]

在控制器中:

@Body() params: Partial<User>
Run Code Online (Sandbox Code Playgroud)

类验证器注释均不适用。这是因为不支持泛型,如其他地方所述。

解决方案 3:在控制器内验证 [ 通过,不推荐 ]

在控制器中:

@Body() params: any
Run Code Online (Sandbox Code Playgroud)

然后,在控制器方法中validate()直接调用类验证器,如下所示:

const transformedValue = plainToClassFromExist(new User(), params);
const errors = validate(transformedValue, {
  skipUndefinedProperties: true
});
Run Code Online (Sandbox Code Playgroud)

这里的神奇之处在于skipUndefinedProperties。如果该属性不存在(即未定义),则类验证器将不会验证它。请注意,@IsDefined() 绕过了这一点。

这可行,但会导致大量重复代码。

解决方案 4:使用自定义装饰器 [通过,推荐]

创建一个名为的装饰器PartialBody,从增强器读取类型信息,然后运行验证,请参阅https://gist.github.com/josephdpurcell/d4eff886786d58f58b86107c0947e19e作为示例。

确保全局验证管道中的 validateCustomDecorators=false 。

现在,它的使用方法有一些变化:

变体 1:通过类型 [ PASS, RECOMMENDED ]

在控制器中:

@PartialBody(User) params: Partial<User>
Run Code Online (Sandbox Code Playgroud)

运行时PartialBody它将从传递的参数中检索类型。这只会验证存在的属性,并确保params是用户的一部分。好哇!

变体 2:使用部分 [ FAIL ]

在控制器中:

@PartialBody() params: Partial<User>
Run Code Online (Sandbox Code Playgroud)

这会失败,因为不支持泛型。当PartialBody装饰器逻辑运行时,它无法访问任何类型信息。

变体 3:使用类 [ FAIL ]

在控制器中:

@PartialBody() params: User
Run Code Online (Sandbox Code Playgroud)

虽然这会像变体 1 一样执行部分验证,但它会失败,因为params会被视为完整类型而不是部分类型。在编写代码时,您的编辑器会认为所有属性都存在,但实际上它们可能不存在。

变体 4:扩展 PartialType [ PASS ]

使用 PartialType 创建一个新类型:

import { PartialType } from '@nestjs/swagger';

export class PartialUser extends PartialType(User) {}
Run Code Online (Sandbox Code Playgroud)

在控制器中:

@PartialBody() params: PartialUser
Run Code Online (Sandbox Code Playgroud)

这与变体 1 一样工作,但您必须创建一个附加类。如果您希望通过覆盖属性和重新声明装饰器来覆盖类验证器检查,这可能是需要的。