Class-Validator (Node.js) 在自定义验证中获取另一个 属性 值
Class-Validator (Node.js) Get another property value within custom validation
目前,我有一个非常简单的 class-validator 文件,在 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;
}
假设在上面的“UpdateUserDto”中,用户传递了一个“电子邮件”字段。我想通过 class-validator 构建自定义验证规则,这样:
- 检查电子邮件地址是否已被用户从数据库中获取
- 如果邮箱地址已经被使用,检查当前用户(使用'id' 属性的值)是否正在使用,如果是,则验证通过,否则,如果是已被其他用户使用,验证失败。
虽然检查电子邮件地址是否已被使用是一项非常简单的任务,但您如何才能将 DTO 中其他属性的值传递给自定义装饰器 @IsEmailUsed
我认为您的第一个用例(检查电子邮件地址是否已被用户从数据库中获取)可以通过使用 custom-validator
来解决
对于第二个,没有在验证之前获取当前用户的选项。假设您正在使用 @CurrentUser
装饰器获取当前用户。然后一旦正常的 dto 验证完成,您需要检查控制器或服务内部是否当前用户正在访问您的资源。
解决起来很简单,我通过创建自定义 class-validation 装饰器解决了这个问题,如下所示:
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,
});
};
}
但是,要允许 DI 到那个特定的装饰器,您还需要将其添加到您的 main.ts bootstrap 函数中:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
// Line below needs to be added.
useContainer(app.select(AppModule), { fallbackOnErrors: true });
...
}
此外,请确保在应用模块中导入“约束”:
@Module({
imports: ...,
controllers: [AppController],
providers: [
AppService,
PrismaService,
...,
// Line below added
UniqueConstraint,
],
})
export class AppModule {}
最后,将其添加到您的 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;
}
目前,我有一个非常简单的 class-validator 文件,在 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;
}
假设在上面的“UpdateUserDto”中,用户传递了一个“电子邮件”字段。我想通过 class-validator 构建自定义验证规则,这样:
- 检查电子邮件地址是否已被用户从数据库中获取
- 如果邮箱地址已经被使用,检查当前用户(使用'id' 属性的值)是否正在使用,如果是,则验证通过,否则,如果是已被其他用户使用,验证失败。
虽然检查电子邮件地址是否已被使用是一项非常简单的任务,但您如何才能将 DTO 中其他属性的值传递给自定义装饰器 @IsEmailUsed
我认为您的第一个用例(检查电子邮件地址是否已被用户从数据库中获取)可以通过使用 custom-validator
来解决对于第二个,没有在验证之前获取当前用户的选项。假设您正在使用 @CurrentUser
装饰器获取当前用户。然后一旦正常的 dto 验证完成,您需要检查控制器或服务内部是否当前用户正在访问您的资源。
解决起来很简单,我通过创建自定义 class-validation 装饰器解决了这个问题,如下所示:
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,
});
};
}
但是,要允许 DI 到那个特定的装饰器,您还需要将其添加到您的 main.ts bootstrap 函数中:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
// Line below needs to be added.
useContainer(app.select(AppModule), { fallbackOnErrors: true });
...
}
此外,请确保在应用模块中导入“约束”:
@Module({
imports: ...,
controllers: [AppController],
providers: [
AppService,
PrismaService,
...,
// Line below added
UniqueConstraint,
],
})
export class AppModule {}
最后,将其添加到您的 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;
}