Nest.js에서 body로 들어오는 요청의 유효성 검사를 위해서는 class-validator, class-transformer가 구축된 validation pipe를 사용한다.
아래 코드처럼 입력들에 대해 각각 dto를 만들고 validation pipe를 전역에 깔아놔서 모든 입력 유효성 검사를 할 수 있도록 했다.
import { PickType } from '@nestjs/mapped-types';
import { CreateTicketDto } from './createTicket.dto';
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdateTicketDto extends PickType(CreateTicketDto, ['receiverAddress']) {
@IsString()
@IsNotEmpty({message: '비밀번호를 입력해주세요.'})
password: string;
}
그리고 여느때와 다름없이 dto를 열심히 짜던 중 개큰 문제를 마주함,,
import { IsBoolean, IsDateString, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from "class-validator";
export class CreateTicketDto {
@IsBoolean()
@IsOptional()
useUserInfo: boolean;
@IsDateString()
@IsNotEmpty({message: '본인확인을 위해 생년월일을 입력해주세요.'})
receiverBirthDate: string;
@IsPhoneNumber('KR')
@IsOptional()
receiverPhoneNumber: string;
@IsString()
@IsOptional()
receiverAddress: string;
}
내가 원하는건 useUserInfo가 false이거나 값이 들어오지 않으면 receiverPhoneNumber, receiverAddress를 필수로 받고, true이면 받지 않아도 되도록 만드는 것이었다.. class-validator에 그런 데코레이터를 제공할 리가 없어서 custom validator를 직접 만들어야했다.
걍 저 기능을 없앨까 했는데
일단 해보기로함^^..
기본 양식은 이렇다.
export class CustomTextValidator implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
// Custom validation logic
const isValid = /* 여기서 validation 로직 작성 */;
return isValid;
}
defaultMessage(args: ValidationArguments) {
// Custom error message
return '에러 메시지!!';
}
}
여기서 value랑 args가 뭐냐면
export class GeometryClass {
@IsString()
@IsNotEmpty()
type: string;
@IsNumber()
@IsNotEmpty()
@Validate(CustomValidator, ['type'])
nums: number;
}
데코레이터가 사용되는 자리에서 nums가 value로 들어가고, ['type'], 즉 추가로 입력받은 나머지 값들이 args로 들어간다.
내가 짠 custom validator는 이렇게 생겼다.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, Validate } from 'class-validator';
@ValidatorConstraint({ name: 'useUserInfoOption', async: false })
export class UseUserInfoConstraint implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
const [useUserInfoProperty, optionalProperty1, optionalProperty2] = args.constraints;
const useUserInfo = (args.object as any)[useUserInfoProperty];
const optionalValue1 = (args.object as any)[optionalProperty1];
const optionalValue2 = (args.object as any)[optionalProperty2];
return this.isValid(useUserInfo, optionalValue1, optionalValue2);
}
private isValid (useUserInfo: boolean, value1: string, value2: string): boolean {
if (useUserInfo==true) {
return true;
}
if (value1!=undefined && value2!=undefined) {
return true;
}
return false;
}
defaultMessage(args: ValidationArguments) {
return '수령자 정보를 입력해주세요.';
}
}
앞에서 말한것처럼 useUserInfo의 true/false 값에 따라 나머지 두 값의 입력 여부를 판단하는 validator이다.
dto에 적용
import { IsBoolean, IsDateString, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, Validate } from "class-validator";
import { UseUserInfoConstraint } from "src/utils/useUserInfo.decorator";
export class CreateTicketDto {
@IsOptional()
@IsBoolean()
useUserInfo: boolean;
@IsDateString()
@IsNotEmpty({message: '본인확인을 위해 생년월일을 입력해주세요.'})
receiverBirthDate: string;
@IsOptional()
@IsPhoneNumber('KR')
@Validate(UseUserInfoConstraint, ['useUserInfo', 'receiverPhoneNumber', 'receiverAddress'])
receiverPhoneNumber: string;
@IsOptional()
@IsString()
@Validate(UseUserInfoConstraint, ['useUserInfo', 'receiverPhoneNumber', 'receiverAddress'])
receiverAddress: string;
}
그리고 이걸 다른 곳에서도 사용할 수 있도록 코드를 유연하게 수정하면 아래와 같아진다.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
@ValidatorConstraint({ name: 'useUserInfoOption', async: false })
export class UseUserInfoConstraint implements ValidatorConstraintInterface {
validate(boolValue: boolean, args: ValidationArguments) {
let arr = [];
for (let property of args.constraints) {
const value = (args.object as any)[property];
arr.push(value);
}
return this.isValid(boolValue, arr);
}
private isValid(boolValue: boolean, arr: any[]): boolean {
if (boolValue == true) {
return true;
} else {
for (let cur of arr) {
if (cur==undefined) {
return false;
}
}
return true;
}
}
defaultMessage(args: ValidationArguments) {
return '수령자 정보를 입력해주세요.';
}
}
'TIL' 카테고리의 다른 글
TIL 240710 - 캐시 & 메모리 (1) | 2024.07.10 |
---|---|
TIL 240709 - 알고리즘 코드카타 리뷰 - 주차요금 계산 (0) | 2024.07.09 |
TIL 240705 - 알고리즘 코드카타 리뷰 - 타겟 넘버 (0) | 2024.07.05 |
TIL 240704 - UpdateValuesMissingError 에러해결 / Isolation Level 적용 (0) | 2024.07.04 |
TIL 240703 - Guard, 커스텀 데코레이터 사용 (0) | 2024.07.03 |