类验证器 (Node.js) 在自定义验证中获取另一个属性值

Has*_*haf 7 node.js class-validator nestjs

目前,我有一个非常简单的类验证器文件,其中包含 Nest.js 中的 ValidationPipe,如下所示:

import {
  IsDateString,
  IsEmail,
  IsOptional,
  IsString,
  Length,
  Max,
} from 'class-validator';

export class UpdateUserDto {
  @IsString()
  id: string;

  @Length(2, 50)
  @IsString()
  firstName: string;

  @IsOptional()
  @Length(2, 50)
  @IsString()
  middleName?: string;

  @Length(2, 50)
  @IsString()
  lastName: string;

  @IsEmail()
  @Max(255)
  email: string;

  @Length(8, 50)
  password: string;

  @IsDateString()
  dateOfBirth: string | Date;
}
Run Code Online (Sandbox Code Playgroud)

假设在上面的“UpdateUserDto”中,用户传递了一个“电子邮件”字段。我想通过类验证器构建自定义验证规则,以便:

  • 检查电子邮件地址是否已被用户从数据库中获取
  • 如果电子邮件地址已被使用,请检查当前用户(使用 'id' 属性的值)是否正在使用它,如果是,则验证通过,否则,如果它已被其他用户使用,则验证失败。

虽然检查电子邮件地址是否已在使用中是一项非常简单的任务,但如何将 DTO 中其他属性的值传递给自定义装饰器@IsEmailUsed

Has*_*haf 19

解决起来非常简单,我通过创建一个自定义类验证装饰器来解决它,如下所示:

import { PrismaService } from '../../prisma/prisma.service';
import {
  registerDecorator,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationArguments,
} from 'class-validator';
import { Injectable } from '@nestjs/common';

@ValidatorConstraint({ name: 'Unique', async: true })
@Injectable()
export class UniqueConstraint implements ValidatorConstraintInterface {
  constructor(private readonly prisma: PrismaService) {}

  async validate(value: any, args: ValidationArguments): Promise<boolean> {
    const [model, property = 'id', exceptField = null] = args.constraints;

    if (!value || !model) return false;

    const record = await this.prisma[model].findUnique({
      where: {
        [property]: value,
      },
    });

    if (record === null) return true;

    if (!exceptField) return false;

    const exceptFieldValue = (args.object as any)[exceptField];
    if (!exceptFieldValue) return false;

    return record[exceptField] === exceptFieldValue;
  }

  defaultMessage(args: ValidationArguments) {
    return `${args.property} entered is not valid`;
  }
}

export function Unique(
  model: string,
  uniqueField: string,
  exceptField: string = null,
  validationOptions?: ValidationOptions,
) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [model, uniqueField, exceptField],
      validator: UniqueConstraint,
    });
  };
}
Run Code Online (Sandbox Code Playgroud)

但是,要允许对特定装饰器进行 DI,您还需要将其添加到 main.ts 引导函数中:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  ...
  // Line below needs to be added.
  useContainer(app.select(AppModule), { fallbackOnErrors: true });
  ...
}
Run Code Online (Sandbox Code Playgroud)

另外,请确保在应用程序模块中导入“约束”:

@Module({
  imports: ...,
  controllers: [AppController],
  providers: [
    AppService,
    PrismaService,
    ...,
    // Line below added
    UniqueConstraint,
  ],
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

最后,将其添加到您的 DTO 中:

export class UpdateUserDto {
  @IsString()
  id: string;

  @IsEmail()
  @Unique('user', 'email', 'id') // Adding this will check in the user table for a user with email entered, if it is already taken, it will check if it is taken by the same current user, and if so, no issues with validation, otherwise, validation fails.
  email: string;
}
Run Code Online (Sandbox Code Playgroud)