백엔드 API 서버를 개발하다 보면 입력 값 검증은 항상 따라옵니다.
Go 에도 여러가지 validation 라이브러리가 있는데, awesome-go 기준 star 가 많은 라이브러리는 크게 두 종류의 방식이 있습니다.
Struct tag 사용
validator(⭐10.2k)
validator
Go의 validator 라이브러리 중 가장 많은 Star 를 보유한 라이브러리 입니다. 제가 주로 사용하는 Fiber 프레임 워크의 문서에서도 우선적으로 소개하기 때문에 가장 많이 사용 했습니다.
struct 정의
1
2
3
4
5
6
7
|
type SignUp struct {
ID string `validate:"required,gte=4,lte=255,alphanum"`
Password string `validate:"required,password"`
Gender string `validate:"gender"`
Age int `validate:"gte=0"`
Email string `validate:"email"`
}
|
validator는 struct tag를 통해 validation을 관리합니다.
사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func NewSignUpValidator() *validator.Validate {
v := validator.New()
return v
}
signup := &SignUp{
ID: "foobar",
Password: "Abcdefghijklmnop1!",
Gender: "male",
Age: 10,
Email: "foo@bar.com",
}
v := NewSignUpValidator()
err := v.Struct(signup)
|
커스텀 validate
1
2
3
4
|
v := validator.New()
_ = v.RegisterValidation("gender", func(fl validator.FieldLevel) bool {
return fl.Field().String() == "male" || fl.Field().String() == "female"
})
|
위 구조체의 validation 중 ‘password’와 ‘gender’는 기본적으로 제공 되지 않는 옵션입니다.
커스텀 옵션은 위와 같이 추가 됩니다. (password 함수는 길이상 블로그에 올리지 않았습니다.)
validator 는 기본적으로 정규표현식을 이용한 검증을 지원하지 않습니다.
공식 문서에서는 Go의 struct tag 구조상 ‘,’ 와 ‘=’ 를 넣을 수 없어서라는 이유를 제시합니다만, 안그래도 난잡한 struct tag에 정규표현식까지 들어간 지옥도는 그다지 보고 싶지 않기 때문에 전 좋다고 생각합니다.
위 예제 코드는 공개되어 있습니다.
비슷한 라이브러리로는 govalidator(⭐5.3k) 가 있습니다.
Struct tag 싫어!
위 예제에서는 struct tag에 validation 정보만 들어갔기 때문에 참사는 벌어지지 않았지만, json 등의 추가 태그가 들어가면 가독성이 점점 떨어지는 문제가 있습니다. 그런 이유에선지 Go 커뮤니티 에서도 호불호를 쉽게 찾아볼 수 있는데, 역시나 사용하지 않는 라이브러리도 있습니다.
ozzo-validation(⭐2.7k)
ozzo-validation
struct tag 를 사용하지 않는 라이브러리 중 가장 높은 Star를 받은 라이브러리 입니다.
구조체를 매개변수로 받는 함수를 구현하는 방식으로 사용되어 사용 중 magical 한 부분이 적다는 인상을 받았습니다.
이 포스트를 작성하며 처음으로 들여다 본 라이브러리인데 Go 에서 호불호가 갈리는 struct tag를 사용하지 않는 것 만으로 충분히 의의가 있지 않나 싶습니다.
기본 예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type Address struct {
Street string
City string
State string
Zip string
}
func (a Address) Validate() error {
return validation.ValidateStruct(&a,
// Street cannot be empty, and the length must between 5 and 50
validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
// City cannot be empty, and the length must between 5 and 50
validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
// State cannot be empty, and must be a string consisting of two letters in upper case
validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
// State cannot be empty, and must be a string consisting of five digits
validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
)
}
|