이번엔 joi의 간단한 사용법과, joi.extend()로 따로 커스텀한 validator를 기존 joi에 주입시키고, 데코레이터로 치환해서 사용하는 과정을 쓰고자 한다.
런타임 이전(lint에서)부터 커스텀한 validator를 접근할 수 있다!
About joi
https://www.npmjs.com/package/joi
joi
Object schema validation
www.npmjs.com
joi는 변수의 schema, type 등을 런타임에서 체킹하도록 도와주는 패키지이다.
가볍게 사용해보자면,
다음과 같은 구조의 object만 해당 변수에 담고 싶다고 가정하자.
const myObject = {
id: number,
name: string,
accountId: number,
balance: number
}
우리에겐 class-validator라는 소중한 패키지가 있지만.. joi로 한번 validator를 작성해보자.
import * as Joi from "joi";
const ValidateMyObject = (myObject: any) => {
return Joi.object({
id: Joi.number().required(),
name: Joi.string().required(),
accountId: Joi.string().required(),
balance: Joi.number().required()
}).validate(myObject);
};
// usage
const example = {
id: 123,
name: "example",
accountId: 123,
balance: 0
};
const validateResult = ValidateMyObject(example);
// error
if (validateResult.error) {
console.error(validateResult.error);
}
굉장히 간단한 예시이며, 각 프로퍼티 별로 값의 범위(필드), nullable 등 많은 옵션들을 이용해 추가로 체킹할 수 있다.
joi.extend()
joi를 쓰다 보면, 데이터 타입 체킹을 추가해야 하는 경우가 생긴다.
예를 들어, 들어온 타입이 hex color code인지 판단하고 싶다.
다음과 같이 만들어 볼 수 있겠다.
const hexColor = (Joi) => {
return {
type: "string",
base: Joi.string(),
messages: {
hexColor: "{{#label}} must be hex color"
},
rules: {
hexColor: {
validate(value, helpers) {
if (!new RegExp(/^#[0-9a-fA-f]{6}$/i).test(value)) {
return helpers.error("hexColor");
}
return value;
}
}
}
};
};
// usage
import * as Joi from "joi";
Joi.extend(hexColor);
...
const color = "Ffc0cb"
const isHexColor = Joi.string().hexColor().validate(color);
// error
if (isHexColor.error) {
console.error(isHexColor.error);
}
문제는 내가 만든 hexColor()라는 validator는 런타임 때 joi.extend()가 수행되고 주입이 되기 때문에,
typescript에서는 당연하게도 joi안에 해당 프로퍼티가 없다고 에러를 뱉는다....
즉, 직접 joi 패키지 안에 hexColor라는 프로퍼티를 주입해야 사용이 가능하다는 얘기다.
시작해보자
joi에 프로퍼티 주입하기
hexColor는 일단 해당 값이 string이어야하기 때문에, joi에서 type checking의 가장 베이스가 되는 스키마 중 하나인 stringSchema에 hexColor()를 주입할 것이다.
우선 오리지날 joi의 StringSchema를 커스텀하자.
interface CustomStringSchema extends OriginJoi.StringSchema {
hexColor(): this;
}
다음은 내가 만든 CustomStringSchema를 StringSchema로 인식하는 Joi.Root 인터페이스를 새로 주입할 것이다.
interface CustomJoi extends OriginJoi.Root {
string(): CustomStringSchema;
}
let Joi: CustomJoi;
// hexColor extend
Joi.extend(hexColor);
// export
export { Joi };
이제 내가 커스텀한 Joi로 hexColor를 불러 써보자
런타임 때 extend가 호출이 되고, 우리는 기존 joi 대신 내가 추가로 주입한 joi를 사용하면 된다
joi로 데코레이터까지
이런 식으로 extension들도 늘리고 하다 보면 보통 object에서 끝이 난다.
예를 들자면
// 커스텀한 joi import
import { Joi } from "@cobb/joi";
export const ValidateDesignForm = (form: any) => {
return Joi.object({
// hexImage, cdnImage, hexColor 모두 새로 추가한 extension들
brandName: Joi.string().max(16).allow(""),
brandLogo: Joi.alternatives().try(Joi.string().hexImage(), Joi.string().max(255).cdnImage()).allow(null),
brandNameColor: Joi.string().hexColor().required(),
backgroundColor: Joi.string().hexColor().required(),
iconColor: Joi.string().hexColor().required()
}).validate(form);
};
문제는 이 함수를 매번 object 받을 때마다 따로 불러서 테스트하기는 좀 그렇고.. (물론 편하면 여기서 끝나도 된다)
class-validator랑 엮어서 데코레이터로 만들고 싶다.
위 함수는 validate()를 호출하면서 끝나고, validate()는 error의 유무에 따라 타입이 맞는지 아닌지 알 수 있으므로,
import { ValidateDesignForm } from "@cobb/joi"
import { registerDecorator, ValidationArguments, ValidationOptions } from "class-validator";
export const IsDesignForm = (validationOptions?: ValidationOptions) => {
return (object: Record<string, any>, propertyName: string) => {
registerDecorator({
name: "IsDesignForm",
target: object.constructor,
propertyName: propertyName,
options: {
message: "Given value does not match with design top format."
},
validator: {
validate(value: any, args: ValidationArguments) {
// error 유무에 따라 boolean 반환
return ValidateDesignForm(value).error ? false : true;
}
}
});
};
};
이런 식으로 작성해주면 되겠다.
이제 사용해보자!
'Back-end > Nest.js' 카테고리의 다른 글
Standard Repo vs MonoRepo (nest library) (0) | 2021.05.24 |
---|