커스텀 데커레이터로 유효성 검사기를 직접 만든 기록이다.
지난 기록에서 Nest의 Pipe에 대해 다룬 적이 있다.
이번 기록에서는 유효성 검사 데커레이터를 직접 만들어서 사용해 보려고 한다.
개요
@MinLength, @IsString 등 class-validator에서 제공하는 커스텀 데커레이터로 유효성 검사를 했다.
이번엔 직접 커스텀 데커레이터를 생성할 것이다.
class-validator는 custom validation decorators를 직접 생성하는 기능을 제공한다.
registerDecorator를 이용해 커스텀 데커레이터를 생성해 보자.
이전 기록에서 구현하던 user api에서
회원가입 기능 중 ID와 Password의 관련성을 없애려고 한다.
Password가 ID가 포함되어 있지 않도록 해당 역할을 하는 데커레이터를 생성할 것이다.
구현
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
export function NotIn(property: string, validationOptions: ValidationOptions) {
return (object: Object, propertyName: string) => {
registerDecorator({
name: 'NotIn',
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [property],
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return (
typeof value === 'string' &&
typeof relatedValue === 'string' &&
!relatedValue.includes(value)
);
},
},
});
};
}
구현된 코드를 보고, 이해를 해보자.
NotIn
가장 바깥의 함수이다.
사용될 때는 @NotIn()의 모습일 것이다.
따라서 비교하려는 다른 속성의 이름(property)과 다른 ValidationOptions를 인자로 전달받는다.
registerDecorator를 호출하는 함수를 리턴한다.
이 익명 함수는 데커레이터가 선언될 객체(object)와 속성 이름(propertyName)을 전달받는다.
해당 데커레이터는 DTO 객체에서 사용될 것이며
타겟 속성과 다른 속성을 비교하는 데커레이터이다.
따라서 선언될 객체는 DTO 객체이고, 속성 이름은 타겟이 될 속성 이름일 것이다.
registerDecorator
registerDecorator는 registerDecoratorOptions 객체를 인수로 받는다.
export interface ValidationDecoratorOptions {
/**
* Target object to be validated.
*/
target: Function;
/**
* Target object's property name to be validated.
*/
propertyName: string;
/**
* Name of the validation that is being registered.
*/
name?: string;
/**
* Indicates if this decorator will perform async validation.
*/
async?: boolean;
/**
* Validator options.
*/
options?: ValidationOptions;
/**
* Array of validation constraints.
*/
constraints?: any[];
/**
* Validator that performs validation.
*/
validator: ValidatorConstraintInterface | Function;
}
/**
* Registers a custom validation decorator.
*/
export declare function registerDecorator(options: ValidationDecoratorOptions): void;
- target : 유효성 검사를 거칠 DTO의 생성자가 타겟 객체가 된다.
- name : 생성될 유효성 검사의 이름이다.
- propertyName : 타겟 속성 즉, DTO에서 타겟으로 결정될 속성의 이름으로 이미 익명 함수를 통해 전달받았다.
- options : 유효성 검사 옵션이다.
- constraints : 데커레이터를 실행시킬 때의 제약이다.
- validator : 유효성 검사를 해줄 함수이다.
validatior
제일 중요한 유효성 검사를 해주는 역할이다.
validator는 ValidatorConstraintInterface를 실행한다.
export interface ValidatorConstraintInterface {
/**
* Method to be called to perform custom validation over given value.
*/
validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> | boolean;
/**
* Gets default message when validation for this constraint fail.
*/
defaultMessage?(validationArguments?: ValidationArguments): string;
}
validate 메서드를 통해 커스텀 유효성 검사를 진행한다.
validate메서드의 value는 DTO 타겟 속성의 값을 말한다.
export interface ValidationArguments {
/**
* Validating value.
*/
value: any;
/**
* Constraints set by this validation type.
*/
constraints: any[];
/**
* Name of the target that is being validated.
*/
targetName: string;
/**
* Object that is being validated.
*/
object: object;
/**
* Name of the object's property being validated.
*/
property: string;
}
validationArguments는 타겟 속성과 DTO 객체에 대한 정보, 그리고 위에서 지정한 constraints 값을 담고 있다.
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
return (
typeof value === 'string' &&
typeof relatedValue === 'string' &&
!relatedValue.includes(value)
);
}
relatedPropertyName은 constraints에서 지정한 배열의 값을 가져온 변수이다.
지금 하려는 작업은 password에 Id가 포함되어 있는지 확인하는 것이다.
그럼 ID 속성이 target value가 될 것이고, password 속성이 related value가 될 것이다.
args.object는 DTO 객체를 참조하고 이를 통해 password 값을 가져온 것이다.
결과적으로 password에 ID 값이 포함되어있지 않으면 유효하다고 판단할 것이다.
참고
마치며
잘못된 정보에 대한 피드백은 환영입니다.
감사합니다.
'Backend > NestJS' 카테고리의 다른 글
[NestJS] NestJS 공식 문서 정독(2)(Dynamic Module) (0) | 2023.01.26 |
---|---|
[NestJS] NestJS 공식 문서 정독(1)(Custom Provider,Async Provider) (0) | 2023.01.24 |
[NestJS] Nest Config 환경 변수 관리 (0) | 2023.01.17 |
[NestJS] Entity간 관계 형성 (1) | 2023.01.08 |
[NestJS]JWT(2) Guards, Passport에 대한 이해 (0) | 2023.01.07 |